<html>

<head>
	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
	<title>i5ting_ztree_toc:计网</title>
	<link href="./github-bf51422f4bb36427d391e4b75a1daa083c2d840e.css" media="all" rel="stylesheet"
		type="text/css" />
	<link href="./github2-d731afd4f624c99a4b19ad69f3083cd6d02b81d5.css" media="all" rel="stylesheet"
		type="text/css" />
	<style>
		#readme{
			min-width: 640px;
			overflow-y: scroll;
		}
	</style>
</head>

<body>
		<div id='readme' style='width:95vw;margin: 0 auto;'>
			<article class='markdown-body'>
				<h2 id="-">网络模型</h2>
				<table>
					<thead>
						<tr>
							<th>OSI网络模型</th>
							<th>TCP/IP模型</th>
							<th>作用</th>
							<th>协议</th>
							<th>数据单位</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>应用层</td>
							<td>应用层</td>
							<td>负责给应用程序提供统一接口</td>
							<td>HTTP，FT，SMTP，TELNET，DNS、DHCP</td>
							<td>报文/APDU</td>
						</tr>
						<tr>
							<td>表示层</td>
							<td></td>
							<td>负责把数据转化成兼容另一个系统能识别的格式</td>
							<td>ASCII、SSL/TLS</td>
							<td>报文/PPDU</td>
						</tr>
						<tr>
							<td>会话层</td>
							<td></td>
							<td>负责建立、管理和终止表示层实体之间的通信会话</td>
							<td>ADSP、RPC</td>
							<td>报文/SPDU</td>
						</tr>
						<tr>
							<td>传输层</td>
							<td>传输层</td>
							<td>负责端到端的数据传输</td>
							<td>TCP、UDP</td>
							<td>段/TPDU</td>
						</tr>
						<tr>
							<td>网络层</td>
							<td>网络层</td>
							<td>负责数据的路由、转发、分片</td>
							<td>IP、ICMP、IGMP，RIP</td>
							<td>包</td>
						</tr>
						<tr>
							<td>数据链路层</td>
							<td>网络接口层</td>
							<td>负责数据的封帧和差错检测、以及MAC寻址</td>
							<td>HDLC、PPP、SLIP，ARP</td>
							<td>帧</td>
						</tr>
						<tr>
							<td>物理层</td>
							<td></td>
							<td>物理层，负责在物理网络中传输数据帧</td>
							<td>S 232C/RS 449/422/423、V.24和X.24、X.21bis</td>
							<td>比特</td>
						</tr>
					</tbody>
				</table>
				<p>应用层：HTTP(超文本传输协议)，FTP(文本传输协议,21号端口)、SMTP(简单邮件传输协议，25号端口)、POP3(与SMTP对应，用于接收邮件)、TELNET(远程终端协议，远程登陆，23端口)，DNS(域名系统)，DHCP(动态主机配置协议)，用户态
				</p>
				<p>表示层：数据的表示、安全、压缩</p>
				<p>会话层：RPC(Remote Procedure Call,远程过程调用协议)。<strong>建立会话，如session认证、断点续传。</strong></p>
				<p>传输层：监控数据传输服务质量，保证报文的正确传输。<strong>图像、视频编码解，数据加密</strong></p>
				<p>网络层：ICMP(互联网控制信息协议)，IGMP(组管理协议)、RIP(路由信息协议)</p>
				<p>数据链路层：通过各种控制协议，将有差错的物理信道变为无差错的、能可靠传输的数据链路。PPP(点对点协议，主要是用来通过拨号或专线方式建立点对点连接发送数据，使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案)、ARP(地址解析协议)
				</p>
				<p>物理层：常见物理媒介有光纤、电缆、中继器等。它主要定义物理设备标准，如网线接口类型、光纤的接口类型、各种传输介质的传输速率等。</p>
				<p><strong>TCP应用层对应协议</strong></p>
				<p>FTP、Telnet、SMTP、POP3</p>
				<p><strong>UDP对应应用层协议</strong></p>
				<p>DNS(域名解析，53端口)、SNMP(简单网络管理协议，161端口)、TFTP(简单文件传输，69端口)</p>
				<h3 id="-">键入网址到网页显示，期间发生了什么</h3>
				<p>1.浏览器先解析URL(统一资源定位符)，确定web服务器和文件名之后根据这些信息生成HTTP请求。</p>
				<p>2.DNS域名解析<strong>查询服务器域名对应IP地址</strong>。(浏览器缓存，操作系统缓存如windows的hosts，本地域名缓存，根域名服务器缓存，顶级域名服务器缓存，权威域名服务器缓存)，当本地系统缓存这个域名与其对应的IP地址时，把解析结构返回给用户的同时，用户会根据TTL值缓存到本地系统缓存中。（<strong>以上应用层</strong>）
				</p>
				<p>3.得到IP地址后，应用程序(浏览器)通过调用Socket库将HTTP的传输工作交给操作系统中的<strong>协议栈</strong>。</p>
				<ul>
					<li>TCP三次握手，同时对数据进行分割(MSS，去除IP和TCP头部之后，一个网络包所能容纳的TCP数据的最大长度),在数据前面增加TCP头部(<strong>传输层</strong>)</li>
					<li>确定发送网卡IP(即源地址IP，根据路由表规则)，增加IP头部(<strong>网络层</strong>)</li>
					<li>利用ARP协议确定MAC地址，增加MAC头部(网络接口层，<strong>数据链路层</strong>)</li>
					<li>利用网卡及网卡程序驱动，将数字信息转换为电信号。网卡驱动获取网络包之后，会将其<strong>复制</strong>到网卡内的缓存区内，然后在其<strong>开头加上报头和起始帧分界符，在末尾加上用于检测错误的帧校验序列(FCS)</strong>。
					</li>
					<li>通过交换机(二层网络设备，端口不具有MAC地址，将电信号转换为数字信号，只要包末尾FCS校验正确，便将包放入缓冲区)转发路由器，交换机根据MAC地址表查找MAC地址，然后将信号发送到相应的端口，如果没有找到，只能将包转发到处理源端口以外的所有端口。(网卡是有自己的MAC端口，交换机没有)
					</li>
				</ul>
				<p>4.网络包经过交换机之后到达路由器(三层网络设备，各个端口具有MAC地址和IP地址)，并在此转发到下一个路由器或目标设备。每成功转发到一个路由器都需要将MAC头部进行更新(ARP协议，根据IP地址查询MAC地址)
				</p>
				<p>5.数据报到达服务器后，利用网卡接收网络包，网卡通过DMA技术将网络包写入指定内存地址(Ring Buffer,一个环形缓冲区)，然后告诉操作系统网络包已经到达。</p>
				<ul>
					<li>先扒掉MAC头部(当然这里会检验是否符合自己的MAC地址)，找到上层协议类型是IPv4还是IPv6，接着去掉帧头帧尾，将包发给网络层。</li>
					<li>然后再是IP地址，再根据IP头中的协议项知道自己上层是TCP。</li>
					<li>扒开TCP头部，里面有序列号，如果序列号符合，就将剩下的数据放入缓存然后返回一个ACK确认。同时里面还有端口号，根据端口号找出对应的Socket。</li>
					<li>最后应用层程序调用Socket接口，将内核的Socket接收缓冲区的数据拷贝到应用层的缓冲区，然后唤醒用户进程。</li>
				</ul>
				<p>6.服务器向客户端发送响应报文</p>
				<ul>
					<li>服务器将HTTP进程需要的包发给HTTP进程，将请求访问的一个页面(或者其他东西)封装在HTTP响应报文里。</li>
					<li>将HTTP响应报文依次增加TCP、IP、MAC头部，发送给服务器(此时源地址是服务器IP)。将封装好的信息通告网卡、交换器、路由器转发至客户端</li>
					<li>最后客户端受到了服务器响应数据包后，扒去各种头部信息，剩下HTTP响应报文，将其交给浏览器去取渲染页面</li>
				</ul>
				<p>7.客户端要断开连接，向服务器发起四次挥手。</p>
				<h3 id="-">第五步，关于网卡接收网络包</h3>
				<p>最简单的方式就是<strong>触发中断</strong>，当网卡收到网络包时，就触发一个中断告诉操作系统。</p>
				<p>但为了解决频繁中断带来的性能开销，Linux内核在2.6版本中映入了<strong>NAPI机制</strong>，不采用中断的方式读取数据，而是<strong>首先采用唤醒数据接收的服务程序，然后poll的方法轮询数据</strong>。
				</p>
				<p>因此，当有网络包到达是，会通过DMA技术，将网络包写入指定的内存地址，接着网卡想CPU发起硬件中断，当CPU收到硬件中断请求后，根据中断表，调用已经注册的中断处理函数。</p>
				<h4 id="-">中断</h4>
				<table>
					<thead>
						<tr>
							<th>中断类型</th>
							<th>描述</th>
							<th>特点</th>
							<th>区别</th>
							<th>例子所做工作</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>硬中断(上半部)</td>
							<td>直接处理硬件请求</td>
							<td>负责耗时短的工作，快速执行</td>
							<td>会打断CPU正在执行任务，然后立即执行中断处理程序</td>
							<td>禁止网卡中断，避免频繁中断而降低内核工作效率</td>
						</tr>
						<tr>
							<td>软中断(下半部)</td>
							<td>内核触发</td>
							<td>负责上半部未完成的工作，耗时较长，延迟执行</td>
							<td>以内核线程的方式执行，并且每一个CPU都对应一个软中断内核线程。</td>
							<td>从内存中找到网络数据，再按照网络协议栈，对网络数据进行逐层解析和处理，最后把数据送而给应用程序</td>
						</tr>
					</tbody>
				</table>
				<p>软中断处理的具体过程，</p>
				<ul>
					<li>内核中的ksoftirqd线程专门复杂软中断处理，当ksoftirqd内核线程收到软中断后，就会轮询处理数据。</li>
					<li>ksoftirqd线程会从Ring Buffer中获取一个数据帧，用sk_buff表示，从而可以作为一个网络包给网络协议栈进行逐层处理</li>
				</ul>
				<p><strong>注：软中断不只是包括硬件设备中断处理程序的下半部，一些内核自定义时间也属于软中断，比如内核调度，RCU锁等。</strong></p>
				<h4 id="dma-">DMA技术</h4>
				<p>在进行I/O设备和内存数据传输时，数据搬运工作全部交给DMA控制器，而CPU不再参与任何与数据搬运相关的事情。</p>
				<p><img src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost2/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E9%9B%B6%E6%8B%B7%E8%B4%9D/DRM%20I_O%20%E8%BF%87%E7%A8%8B.png"
						style="zoom:50%;" /></p>
				<p>具体过程：</p>
				<ul>
					<li>用户进程调用read方法，想操作系统发出I/O请求，请求读取数据到自己的内存缓冲区中的，进程进入阻塞状态。</li>
					<li>操作系统收到请求后，进一步将I/O请求发送到DMA，然后CPU执行其他任务。</li>
					<li>DMA进一步将I/O请求发送给磁盘。</li>
					<li>磁盘收到DMA的I/O请求把数据从磁盘读取到磁盘控制器的缓冲区中，当磁盘控制器的缓冲区被读满后，向DMA发起中断信息，告知自己缓冲区已满。</li>
					<li><strong>DMA收到磁盘信号，将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中，此时不占用CPU，CPU可以执行其他任务。</strong>(如果没有DMA，磁盘读满后发送I/O中断信号给CPU，然后CPU将数据从磁盘控制器换冲过去拷贝到PageCache，然后在将数据从PageCache拷贝到用户缓冲区)
					</li>
					<li>当DMA读取了足够多的数据，就会发送中断信号给CPU。</li>
					<li>CPU收到DMA信号，知道数据已经准备好，于是将数据从内核拷贝到用户空间，系统调用返回。</li>
				</ul>
				<p><strong>系统调用</strong></p>
				<p>系统调用：内核提供给应用程序使用的功能函数，系统调用的请求在用户态发出，对系统调用的相应处理在内核态
					过程：传递系统调用参数-》执行陷入指令(用户态)-》执行系统调用相应的服务程序(核心态)-》返回用户程序</p>
				<p>系统调用与函数调用的关系：
					1.系统调用就是函数调用，但是调用的是内核态的函数。
					2.与普通函数调用不同，系统调用不能使用call指令来调用，需要使用软中断来调用。</p>
				<p>系统调用与库函数的区别：
					1.系统调用是操作系统向上层提供的接口
					2.库函数实现需要调用系统调用进行封装
					3.存在不涉及系统调用的库函数，如取绝对值函数(用户态下进行)</p>
				<p>系统调用特点，发出系统调用请求在用户态，对系统调用的相应处理在内核态。(会影响其他进程，需要操作系统介入)
					陷入指令是唯一一个在用户态执行，而不可在内核态执行的指令。</p>
				<h4 id="-">发送网络数据时，设计几次内存拷贝操作</h4>
				<p>注：发送网络包和接收网络包流程正好相反。</p>
				<ul>
					<li>调用发送数据的系统调用的时候，内核会申请一个内核态的sk_buff内存，将用户待发送数据拷贝到sk_buff内存，并将其加入到发送缓冲区</li>
					<li>在使用TCP传输协议时，从传输层进入到网络层，每一个sk_buff都会被克隆一个新的副本。副本sk_buff会被发送到网络层，发送完就会被释放。原始的sk_buff，目的是为了实现TCP可靠传输，等收到这个数据包ACK，才会释放原始的sk_buff。
					</li>
					<li>当IP层发现sk_buff大于MTU时才需要进行。会在申请额外的sk_buff，并将原来的sk_buff拷贝为多个小的sk_buff。</li>
				</ul>
				<h2 id="http">HTTP</h2>
				<h3 id="http-">HTTP的演变</h3>
				<table>
					<thead>
						<tr>
							<th>名称</th>
							<th>变化</th>
							<th>优点</th>
							<th>缺点</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>HTTP</td>
							<td>超文本协议传输</td>
							<td></td>
							<td>1.短连接 2.明文传输不安全</td>
						</tr>
						<tr>
							<td>HTTP1.1</td>
							<td>1.长连接 2.管道网络传输(默认关闭) 3.队头阻塞(缺陷)</td>
							<td>1.简单 2.灵活易于扩展 3.应用广泛和跨平台</td>
							<td>1.无状态 2.明文传输不安全 3.高延迟</td>
						</tr>
						<tr>
							<td>HTTPS</td>
							<td>在TCP和HTTP之间加入了SSL/TLS安全协议，所以HTTPS协议需要向CA(证书权威机构)申请数字证书，来保证服务器的身份可信。</td>
							<td>解决窃听风险、篡改风险、冒充风险</td>
							<td></td>
						</tr>
						<tr>
							<td>HTTP/2</td>
							<td>基于HTTPS</td>
							<td>1.头部压缩 2.二进制格式 3.并发传输 4.服务器主动推进资源</td>
							<td>还是存在队头阻塞</td>
						</tr>
						<tr>
							<td>HTTP/3</td>
							<td>HTTP的下层协议改成基于UDP的QUIC协议</td>
							<td>1.无队头阻塞 2.更快的连接建立 3.连接迁移</td>
						</tr>
					</tbody>
				</table>
				<p><img src="https://camo.githubusercontent.com/8fc01fbb786ca5745f6eeeef9c3200223914ba96df7153aada1a166070ad530f/68747470733a2f2f63646e2e7869616f6c696e636f64696e672e636f6d2f67682f7869616f6c696e636f6465722f496d616765486f73742f2545382541452541312545372541452539372545362539432542412545372542442539312545372542422539432f485454502f32372d48545450332e706e67"
						style="zoom: 80%;" /></p>
				<h3 id="http">HTTP</h3>
				<p>HTTP，超文本传输协议，<strong>一个在计算机世界专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范</strong>。</p>
				<ul>
					<li>超文本，文字、图片、视频等的<strong>混合体</strong>，<strong>最关键有超链接</strong>，能从一个超文本跳转到另一个超文本(比如HTML)</li>
					<li>传输，<strong>两点之间</strong>(浏览器与服务器，服务器与服务器)，双向协议，允许中转或接力</li>
					<li>协议，必须有<strong>两个</strong>以上的参与者，对参与者的一种<strong>行为约定和规范</strong></li>
				</ul>
				<h4 id="-">状态码</h4>
				<table>
					<thead>
						<tr>
							<th>类型</th>
							<th>具体含义</th>
							<th>常见状态码</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>1xx</td>
							<td>提示信息，表示目前是协议处理的中间状态，还需要后续的操作</td>
							<td></td>
						</tr>
						<tr>
							<td>2xx</td>
							<td>成功，报文已经收到并被正确处理</td>
							<td>[200 OK] 表示一切正常。<strong>非HEAD请求</strong>，服务器返回的响应头有body数据。<br />[204 No
								Content]，表示成功，但是没有body数据。</td>
						</tr>
						<tr>
							<td>3xx</td>
							<td>重定向，资源位置发生变动，需要客户端重新发送新的URL请求资源</td>
							<td>[301 Moved Permanently]，<strong>永久重定向</strong>，表示请求的资源不存在，需要新的URL。<br />[302
								Found]，<strong>临时重定向</strong>，说明请求的资源还在，但暂时需要用另一个URL来访问。<br />[304 Not
								Modified]，<strong>缓存重定向</strong>，301和302会自动跳转到新的URL，但是304不跳转，用的是缓存。</td>
						</tr>
						<tr>
							<td>4xx</td>
							<td>客户端错误，请求报文有误，服务器无法处理</td>
							<td>[400 Bad Request]，表示<strong>客户端请求的报文有错误</strong>，但只是个笼统的错误<br />[403
								Forbidden]，表示<strong>服务器禁止访问资源</strong>，并不是客户端的请求出错。<br />[404 Not
								Found]，表示<strong>请求资源在服务器上不存在或未找到</strong>，所以无法提供给客户端。</td>
						</tr>
						<tr>
							<td>5xx</td>
							<td>服务器错误，服务器在处理请求时内部发生了错误</td>
							<td>[500 Internel Server Error]，<strong>笼统通用的错误码</strong>，服务器发生了什么错误，并不知道。<br />[501 Not
								Implemented]，表示<strong>客户端请求的功能还不支持</strong>，类似“即将开业，敬请期待”的意思。<br />[503 Service
								Unavailable]，表示<strong>服务器当前很忙，暂时无法响应客户端</strong>，类似&quot;网络服务正忙，请稍后重试&quot;的意思。</td>
						</tr>
					</tbody>
				</table>
				<h4 id="http-">HTTP请求报文与响应报文</h4>
				<h5 id="http-">HTTP请求报文(请求行，请求头，请求体)</h5>
				<pre><code class="lang-c++">// 请求行
POST /chapter17/user.html HTTP/1.1   
// 请求头
Accept: image/jpeg, application/x-ms-application, *...
Referer: http://localhost:8088/chapter17/user/register.html?code=100&amp;time=123123
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1:Content-Type: application/x-www-form-urlencoded
Host: localhost:8088(www.xxx.com)
Content-Length: 112
Connection: Keep-Alive
Content-Type: text/html; charset=utf-8   
// 请求体
name=tom&amp;password=1234&amp;realName=tomson
</code></pre>
				<p>(1) 请求行</p>
				<ul>
					<li>POST，请求方法</li>
					<li>/chapter17/user.html，请求URL</li>
					<li>HTTP1.1，HTTP协议及版本</li>
				</ul>
				<p>(2) 请求头(报文头)，各种字段组成：</p>
				<ul>
					<li>Host，用来指定服务器域名</li>
					<li>Content-length字段，表示服务器返回数据时数据长度</li>
					<li>Connection字段，常用于开启HTTP长连接机制</li>
					<li>Content-Type字段，表示返回数据的数据类型</li>
					<li>Content-Encoding字段，表示返回的数据用了什么压缩格式</li>
				</ul>
				<p>(3) name=tom&amp;password=1234&amp;realName=tomson，请求体(报文体)</p>
				<h5 id="http-">HTTP响应报文(响应行，响应头，响应体)</h5>
				<pre><code class="lang-c++">//响应行
HTTP/1.1 200 OK
//响应头
Server:Apache-Coyote/1.1
Content-Type:application/json
Transfer-Encoding:chunked
Date:Mon,12 Sep 2011 12:41:24 GMT
//响应体
6f
{&quot;password&quot;:&quot;1234&quot;,&quot;userName&quot;:&quot;tom&quot;,&quot;birthday&quot;:&quot;null&quot;,&quot;salary&quot;:0,&quot;realName&quot;:&quot;tomson&quot;,&quot;userId&quot;:&quot;1000&quot;,&quot;dept&quot;:null}
0
</code></pre>
				<p>(1) 响应行</p>
				<ul>
					<li>HTTP/1.1 报文协议及版本</li>
					<li>200 OK 状态码及状态描述</li>
				</ul>
				<p>(2) 响应头</p>
				<ul>
					<li>Server字段，服务器信息</li>
					<li>Contenet-Length字段，响应体长度</li>
					<li>Content-Type字段，内容类型，text、html、xml</li>
					<li>Lats-Modified字段，最后修改日期，通常根缓存相关</li>
					<li>Location字段，新地址</li>
					<li>Cache-Control，资源在客户端缓存的有效期，是一个相对时间</li>
					<li>Expires，资源在客户端缓存的有效期，是一个绝对时间</li>
				</ul>
				<p>(3) 响应体，真正的干货</p>
				<h4 id="http-http1-0-">HTTP请求方法(HTTP1.0)</h4>
				<table>
					<thead>
						<tr>
							<th>方法</th>
							<th>描述</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>GET</td>
							<td>请求指定的页面信息，并返回实体主体</td>
						</tr>
						<tr>
							<td>HEAD</td>
							<td>类似于GET请求，只不过返回的响应中没有具体的内容，用于获取报头</td>
						</tr>
						<tr>
							<td>POST</td>
							<td>向指定资源提交数据进行处理请求(例如提交表单或上传文件)。数据被包含在请求体中，POST请求可能会导致新的资源的建立或已有资源的修改。</td>
						</tr>
					</tbody>
				</table>
				<p><strong>GET和POST的区别</strong></p>
				<p>1.GET用URL传输数据，POST用body传输数据</p>
				<p>2.GET主要用于获取数据，POST主要用于修改数据</p>
				<p>3.GET请求的数据会被浏览器主动缓存，POST不能缓存</p>
				<p>4.GET提交数据最大是2k(实际上取决于浏览器)，POST理论上没有限制</p>
				<p>5.GET是幂等的，POST是不幂等的(<strong>幂等：多次执行相同操作，结果都是相同的</strong>)</p>
				<p>6.GET与POST的安全问题(根据安全定义的不同，以及请求所做操作)</p>
				<ul>
					<li><strong>安全：请求方法不会破坏服务器上的资源。</strong>根据RFC规范定义的语义上看，GET是安全幂等的，POST是不安全不幂等的。但是如果用GET方法增删数据，POST方法查询数据，这样GET方法就是不安全和幂等的，POST是安全且幂等。
					</li>
					<li><strong>安全：信息是否会被泄露。</strong>因为GET是用URL传输的会被浏览器地址栏直接看到，但是POST提交的body数据一般通过抓包看到。所以可以说POST比GET安全，但是要真正避免传输过程数据被窃取还是需要HTTPS协议进行数据加密。(HTTP是明文传输)
					</li>
				</ul>
				<h4 id="http-">HTTP缓存技术</h4>
				<p>有些重复的请求，我们可以把这些重复的请求以及他们对应的响应缓存在本地，这样就不至于每次都要获取服务器的响应，直接在本地缓存中找。</p>
				<p>HTTP/1.1 通过 Cache-Control 首部字段来控制缓存</p>
				<p><strong>强制缓存</strong></p>
				<p><strong>只要浏览器判断缓存没有过期，则直接使用浏览器的本地缓存，决定是否使用缓存的主动性在于浏览器这边</strong>。主要通过Cache-Control和Expires这两个HTTP响应头部字段实现，Cache-Control的优先级高于Expires。
				</p>
				<p><strong>协商缓存</strong></p>
				<p><strong>与服务端协商之后，通过协商结果判断是否使用本地缓存，遇到304时候就是协商缓存</strong>。协商缓存这两个字段都需要配合强制缓存中Cache-Control字段来使用，只有在<strong>未能命中强制缓存</strong>时候，才能发起带有协商缓存字段的请求。
				</p>
				<p><strong>注</strong>：两个字段，请求头部中If-Modified-Since字段和响应头部中Last-Modified字段，或者，请求头部If-None-Match字段和响应头部ETag字段
				</p>
				<h3 id="http1-1">HTTP1.1</h3>
				<h4 id="-http-"><strong>相比较HTTP的优势</strong></h4>
				<ul>
					<li><strong>长连接</strong>，只要一端没有明确提出断开连接，则保持TCP连接状态(早期HTTP，串行请求，每发起一个请求都要新建一个TCP连接)</li>
					<li><strong>管道网络传输</strong>(默认不开启)，即在同一个TCP连接里，客户端可以发起多个请求，只要第一个请求发出去不用等他回来就可以发送第二个请求，可以减少整体响应时间。但是服务器必须按照接受请求顺序发送对这些管道化请求的响应。所以，<strong>HTTP/1.1管道解决了请求队头阻塞，但是没有解决响应队头阻塞</strong>。
					</li>
				</ul>
				<h4 id="-"><strong>缺陷</strong></h4>
				<ul>
					<li><strong>高延迟</strong>，网络延迟问题主要由于队头阻塞，导致带宽无法被充分利用。当顺序发送的请求序列中的一个请求被某种原因阻塞，后面排队的请求一并会被阻塞。</li>
					<li><strong>巨大HTTP头部</strong>，主要由于无状态，由于报文Header一般会携带大量固定头字段，而Body却经常很小。Header携带内容过大，在一定程度上增加了传输成本，并且<strong>很多字段值是重复</strong>的。
					</li>
					<li><strong>不安全</strong>，明文传输是不安全的，客户端和服务端都无法验证对方身份，在一定程度上无法保证数据的安全性。</li>
				</ul>
				<p><strong>注</strong>：无状态有好有坏。(Cookie技术，通过在请求和响应报文中写入Cookie信息控制客户端状态)</p>
				<p>好处：服务器不会去记忆HTTP状态，能够减轻服务器负担。</p>
				<p>坏处：既然服务器没有记忆能力，他完成关联性操作会非常麻烦，当服务器请求不知道之前请求是否关联，每次都要问一遍身份信息。</p>
				<h4 id="http-1-1-">HTTP/1.1优化</h4>
				<ul>
					<li>尽量避免发送HTTP请求，<strong>把请求-响应的数据缓存在本地</strong>，那么下次就直接读取本地数据，不必通过网络获取服务器响应。</li>
				</ul>
				<p>客户端会把第一次请求及响应数据保存在本地磁盘上，将请求的URL作为key，响应作为value，两者形成映射。</p>
				<p>服务器在发送HTTP响应时，会估算一个过期时间，并把这个信息放在响应头中，在查看响应头信息时一旦发现缓存响应过期，会重新发送网络请求。</p>
				<p>客户端重新发送请求，会在请求的Etag头部带上第一次请求响应头部中的摘要，这个摘要是唯一标识响应的资源，当服务器收到请求，会将本地资源摘要与请求摘要作比较，如果相同，说明客户端缓存还能使用，服务器仅返回不含有包体的304
					Not Modified 响应，如果不同，服务器响应会带上新的资源</p>
				<ul>
					<li>在需要发送HTTP请求时，考虑如何减少请求次数</li>
				</ul>
				<p>1.减少重定向请求次数，重定向的工作交由代理服务器完成</p>
				<p>2.合并请求，以一个大资源替换多个小资源，但是存在一个问题，当其中一个小资源发生变化，客户端必须重新下载整个大资源</p>
				<p>3.延迟发送请求，按需获取，只获取当前用户所看到的页面资源</p>
				<ul>
					<li>减少服务器HTTP响应数据大小，对响应资源进行压缩，减小响应数据大小</li>
				</ul>
				<p>1.有损压缩</p>
				<p>2.无损压缩</p>
				<h3 id="-https">*HTTPS</h3>
				<p>在HTTP与TCP之间增加了SSL/TLS协议，所以，HTTPS在TCP三次握手之后，还需进行SSL/TLS的握手过程，才能进行加密报文传输。</p>
				<p>HTTPS协议需要向CA(证书权威机构)申请数字证书，来保证服务器身份可信。</p>
				<p><strong>注意：</strong>HTTP默认端口号是80，HTTPS默认端口号是443。</p>
				<p><strong>1.混合加密(解决窃听风险)</strong>：</p>
				<p>在<strong>通信建立前</strong>采用<strong>非对称加密</strong>交换密钥，<strong>后续在通信过程</strong>中采用<strong>对称加密</strong>交换密钥。
				</p>
				<p><strong>非对称加密</strong>：使用两个密钥(公钥和私钥)，公钥可以任意分发而私钥保密，解决了密钥交换问题但速度慢。</p>
				<ul>
					<li>优点：可以更安全地将公开密钥传输给通信发送方；</li>
					<li>缺点：运算速度慢。</li>
				</ul>
				<p><strong>对称加密</strong>：只使用一个密钥，运算速度快，密钥必须保密，无法做到安全的密钥交换。</p>
				<ul>
					<li>优点：运算速度快</li>
					<li>缺点：无法安全地将密钥传输给通信方</li>
				</ul>
				<p><strong>2.摘要算法+数字签名(解决篡改风险)</strong></p>
				<p>计算机<strong>用摘要算法(哈希函数)来计算出内容的哈希值</strong>，这个哈希值是唯一的，且无法通过哈希值推导出内容。</p>
				<p>哈希算法可以确保内容不会被篡改，但是并不能保证&quot;内容+哈希值&quot;不会被中间人替换，因为这里缺少客户端收到消息是否来源于服务端的证明。</p>
				<p><strong>通过“私钥加密，公钥解密”的方式来确认消息身份(数字签名算法)</strong>，私钥加密的内容是内容的哈希值。</p>
				<p>私钥由服务端保管，然后服务端会向客户端颁发对应公钥。</p>
				<p><strong>3.数字证书(解决冒充风险)</strong></p>
				<p>客户端的公钥可能还能被伪造！</p>
				<p>CA(数字证书认证机构)会先将<strong>持有者的公钥和一些信息</strong>打包在一起，通过hash计算一个hash值，<strong>CA用自己的私钥对这个hash值加密</strong>，生成数字签名，这些东西放在证书上就成了数字证书。
				</p>
				<p>客户端验证服务端的数字证书先通过同样的hash算法获取证书的hash值h1，通过CA的公钥解密数字签名得到hash值h2，h1和h2相同，则证书就是可靠的。</p>
				<p>但事实上，证书的验证过程中还存在一个证书信任链的问题，因为我们向CA申请的证书一般不是根证书签发的，而是由中间证书签发的。这是为了确保根证书的绝对安全性，将根证书隔离地越严格越好，不然根证书如果失守了，那么整个信任链都会有问题。(<strong>*中间过程</strong>)*
				</p>
				<p><strong>注意事项：</strong></p>
				<p><strong>公钥加密，私钥解密。</strong>目的是为了<strong>保证内容传输的安全</strong>。因为公钥加密的内容，其他人无法解密，只有持有私钥的人，才能解密出实际的内容。(混合加密前半部分)
				</p>
				<p><strong>私钥加密，公钥解密。</strong>目的是为了<strong>保证消息不会被冒充</strong>。因为私钥是不可被泄露的，如果公钥能正常解密出私钥加密的内容，就能证明这个消息来源于私钥身份的人发送。(数字签名，数字证书)
				</p>
				<h4 id="ssl-tls-">SSL/TLS协议基本流程</h4>
				<ul>
					<li>客户端向服务器索要并验证服务器的公钥。</li>
					<li>双方协商生产会话密钥</li>
					<li>双方采用会话密钥进行加密通信</li>
				</ul>
				<h4 id="rsa-ecdhe-">RSA握手解析与ECDHE握手解析</h4>
				<p>区别：</p>
				<ul>
					<li>RSA密钥协商算法不支持前向保密，ECDHE密钥协商算法支持前向保密</li>
					<li>使用RSA密钥协商算法，TLS完成四次握手后才能进行应用数据传输，而对于ECDHE算法，客户端可以不用等服务器的最后一次TLS握手，就可以发出加密HTTP数据，节省了1RTT。</li>
					<li>使用ECDHE，在TLS第2次握手中，会出现服务器发出的Server Key Exchange 消息，而RSA握手过程没有该消息。</li>
				</ul>
				<p><strong>注</strong>：前向保密，意味着一条信息的泄露不会泄露其他信息，同时不存在一个秘密值</p>
				<h4 id="https-">HTTPS优化</h4>
				<p>产生性能消耗的两个消耗，TLS协议握手过程，握手后对称加密报文传输</p>
				<ul>
					<li>硬件优化，优化CPU，因为HTTPS连接过程中有大量需要计算密钥过程。同时一个选择支持<strong>AES-NI</strong>特性的CPU，这款CPU优化了AES算法。</li>
					<li>软件优化，升级Linux内核、OpenSSL版本</li>
					<li>
						<p>协议优化，密钥交换过程优化，<strong>尽量选用ECDHE密钥交换</strong>(具有前向安全性），同时尽量选择<strong>x25519曲线</strong>。对称加密算法可以选择AES_128_GCM，密钥长度短。<strong>将TLS1.2升级成TLS1.3</strong>，TLS1.3把Hello把公钥交换这两个消息合并成了一个消息。
						</p>
					</li>
					<li>
						<p>证书优化，一个是<strong>证书传输</strong>，一个是<strong>证书验证</strong>。</p>
					</li>
				</ul>
				<p>证书传输，对于服务器证书应该选择椭圆曲线证书，而不是RSA证书，因为在相同安全强度，ECC密钥长度比RSA短。</p>
				<p>证书验证，服务器应该开启OCSP Staping 功能，由服务器预先获得OCSP响应，并把响应结果缓存起来，这样TLS握手的时候不再访问CA服务器，减少了网络通信的开销，提高证书验证的效率。</p>
				<ul>
					<li>会话复用，Session ID，Session Ticket</li>
				</ul>
				<p>1.Session ID，<strong>客户端和服务器首次TLS握手连接后，双方会在内存缓存会话密钥，并有位移的Session ID来标识</strong>。</p>
				<p>缺点：</p>
				<ul>
					<li>服务器必须保持每一个客户端的会话密钥，随客户端增多，服务器的内存压力也越大</li>
					<li>现在网站服务一般是由多台服务器通过负载均衡提供服务的，客户端再次连接不一定会命中上次访问过的服务器，于是还要走完整TLS握手过程。</li>
				</ul>
				<p>2.Session Ticket，<strong>服务器不在缓存每个客户端的会话密钥，而是把缓存工作交给了客户端</strong>。类似HTTP的Cookie</p>
				<h4 id="-">附加问题</h4>
				<ul>
					<li>为什么有时候刷新页面不需要重新建立SSL连接</li>
				</ul>
				<p>TCP 连接有的时候会被浏览器和服务端维持一段时间，TCP 不需要重新建立，SSL 自然也会用之前的。</p>
				<h3 id="-http-2">*HTTP/2</h3>
				<h4 id="-http-1-1-"><strong>相比较HTTP/1.1的优势</strong></h4>
				<ul>
					<li>头部压缩，利用HPACK算法，在客户端和服务器同时维护一张头信息表，所有字段会存入这个表，生成一个索引号，只发送索引号。(<strong>*静态表，动态表</strong>)*</li>
					<li>二进制格式，头信息和数据体都是二进制，并且统称为帧，头信息帧与数据帧。</li>
					<li>并发传输，多个Stream复用一条TCP连接，达到并发效果（<strong>Stream内部是有序的但Stream的帧可以乱序发送</strong>，Stream
						ID在同一个连接中不能被复用）。一个TCP连接包含多个Stream，一个Stream里包含多个Message(对应HTTP/1中的请求与响应)，一个Message包含多个Frame，Frame是HTTP/2的最小单位，以二进制压缩格式存放HTTP/1的内容。
					</li>
					<li><strong>服务器主动推送资源</strong>，客户端发起请求，必须使用奇数号Stream，服务器主动推送，使用偶数号。</li>
				</ul>
				<h4 id="-"><strong>缺陷</strong></h4>
				<p>HTTP/2 还是存在<strong>队头阻塞</strong>，问题在于TCP。</p>
				<p>HTTP/2是基于TCP协议来传输数据的，TCP是字节流协议，TCP成必须保证收到的字节数据是完整且连续的，在于内核才会将缓冲区的数据返回HTTP应用，当某些字节数据未到达，后收到的字节数据只能存放在内核缓冲区里，只有等到数据完整，HTTP/2应用层才能从内核中拿到数据。
				</p>
				<h3 id="http-3">HTTP/3</h3>
				<p>不同：将TCP改成基于UDP的QUIC协议</p>
				<p><strong>相比较HTTP/2的优势</strong></p>
				<ul>
					<li>无队头阻塞。</li>
				</ul>
				<p>在同一条连接上并发传输多个Stream，Stream可以认为就是一条HTTP请求。<strong>当某个流发生丢包时，只会阻塞这个流，其他流不会收到影响，因此不存在队头阻塞问题</strong>(不同于TCP，基于UDP的QUIC协议，连接上的多个Stream之间并没有依赖)，为了保证可靠性，每个数据报都有一个序号唯一标识。
				</p>
				<ul>
					<li>更快的连接建立。</li>
				</ul>
				<p>对于HTTP/1与HTTP/2，TCP和TLS是分层的，分别属于内核实现的传输层、OpenSSL库实现的表示层。先TCP握手再TLS握手。</p>
				<p>HTTP/3在传输数据前需要QUIC协议握手，这个握手过程需要1RTT，目的为了确认双方连接ID，<strong>连接迁移就是基于连接ID实现的</strong>。</p>
				<p>HTTP/3的QUIC内部包含了TLS，它在自己的帧会携带TLS里的“记录”，再加上QUIC使用TLS1.3，因此仅需要1个RTT就可以同时完成建立连接与密钥协商，甚至第二次连接的时候，应用数据包可以和QUIC握手信息(连接信息
					+ TLS信息)一起发送，达到 0-RTT 的效果。</p>
				<ul>
					<li>连接迁移</li>
				</ul>
				<p>基于TCP的HTTP协议，是通过四元组(源IP、源端口、目的IP、目的端口)确定一条TCP连接。当移动设备的网络从4G切换到WIFI时，意味着IP地址变化，那么必须断开连接然后重新建立连接。</p>
				<p>QUIC协议通过连接ID来标记通信的两个断点，客户端和服务器可以各自选择一组ID来标记自己。即使移动设备网络变化，只要仍保有<strong>上下文信息(比如连接ID、TLS密钥)</strong>，就可以无缝地复用原连接，消除重连成本，达到连接迁移功能。
				</p>
				<h3 id="http-">HTTP与其他协议对比</h3>
				<h4 id="rpc-">RPC，远程过程调用</h4>
				<p>RPC主要应用于C/S架构(主要管自家软件服务器)，HTTP主要用于B/S架构(统一B/S的协议)</p>
				<p><strong>区别</strong></p>
				<ul>
					<li><strong>服务发现</strong>，HTTP通过DNS服务去解析IP地址，RPC一般通过中间服务区保存服务名和IP信息，比如Consul、Etcd、Redis、CoreDNS(基于DNS)
					</li>
					<li><strong>底层连接形式</strong>，</li>
				</ul>
				<p>HTTP在建立底层TCP连接后一直保持这个连接(Keep Alive)，之后的请求和响应都会复用这个连接</p>
				<p>RPC也是通过TCP长链接进行数据交互，但是不同的是，RPC会再建立一个连接池。由于连接池有利于提升网络请求性能，所以不少编程语言网络库都会给HTTP增加个连接池比如Go。</p>
				<ul>
					<li>传输内容</li>
				</ul>
				<p>HTTP,Header和Body传的内容以字符串为主，Body这块使用Json来序列化结构体数据。(内容非常多，显得非常啰嗦，这里讲的HTTP/1.1)</p>
				<p>RPC，定制化更高，可以采用体积更小的Protobuf或其他序列化协议去保存结构体数据，同时不需要像HTTP那样考虑各种浏览器行为。</p>
				<p><strong>总结</strong>：</p>
				<p>HTTP/2性能优于RPC，但是很多公司内部RPC协议跑了好些年，所以基于历史，一般没必要换。</p>
				<h4 id="websocket-">WebSocket(全双工)</h4>
				<p>服务器主动发送消息给客户端，一般采用网页前端不定时发HTTP请求到服务器，服务器收到请求后给客户端响应消息,或者使用长轮询，将HTTP请求超时设置很大，一旦服务器收到扫码请求，立马返回客户端网页。
				</p>
				<p>另一种就是WebSocket，用于游戏数据交互</p>
				<p><strong>建立过程</strong>：浏览器在TCP三次握手建立连接之后，统一使用HTTP协议进行通信。如果是普通HTTP请求，双方还是继续用HTTP协议进行交互。如果想建立WebSocket连接，就会在HTTP请求里带上一些特殊的header头，表示升级协议，同时带上一段<strong>随机生成的base64码</strong>发给服务器。如果服务器支持升级，就会走<strong>WebSocket握手流程(两次)</strong>，根据客户端生成的base64码，用公开的算法变成另一段<strong>字符串</strong>，放在<strong>HTTP响应的sec-WebSocket-Accept</strong>头里，同时带上<strong>101状态码(协议切换)</strong>，发回给浏览器。
				</p>
				<p><strong>使用场景</strong>：适用于需要服务器和客户端(浏览器)频繁交互的大部分场景，网页、小程序游戏、网页聊天室、飞书这样网页协同办公软件</p>
				<h2 id="tcp">TCP</h2>
				<h3 id="tcp-udp-tcp-">TCP与UDP(TCP基础)</h3>
				<table>
					<thead>
						<tr>
							<th>区别</th>
							<th>TCP</th>
							<th>UDP</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>概念</td>
							<td>面向连接的、可靠的、字节流的传输层通信协议</td>
							<td>无连接、不可靠的、基于数据报的传输层通信协议</td>
						</tr>
						<tr>
							<td>连接</td>
							<td>传输数据前先要建立连接(面向连接)</td>
							<td>即时传输数据</td>
						</tr>
						<tr>
							<td>服务对象</td>
							<td>一对一的两点服务，即一条连接只有两个端点</td>
							<td>支持一对一，一对多，多对多的交互通信</td>
						</tr>
						<tr>
							<td>可靠性</td>
							<td>可靠交付数据，数据可以无差错、不丢失、不重复、按序到达</td>
							<td>尽最大努力交付，不保证可靠交付数据(可以利用QUIC协议实现可靠传输)</td>
						</tr>
						<tr>
							<td>拥塞控制、流量控制</td>
							<td>TCP有拥塞控制与流量控制机制，保证数据传输的安全性</td>
							<td>UDP没有，即使网络非常拥堵，也不会影响UDP发送速率</td>
						</tr>
						<tr>
							<td>首部开销</td>
							<td>TCP首部长度较长，默认20字节</td>
							<td>UDP只有8个字节，且固定不变</td>
						</tr>
						<tr>
							<td>传输方式</td>
							<td>流式传输，没有边界，但保证顺序和可靠</td>
							<td>一个包一个包的发送，是有边界的，但可能会丢包和乱序</td>
						</tr>
						<tr>
							<td>分片不同</td>
							<td>MSS，传输层分片</td>
							<td>MTU，IP层分片</td>
						</tr>
						<tr>
							<td>应用场景</td>
							<td>FTP文件传输、HTTP/HTTPS</td>
							<td>包总量较少的通信、视频音频等多媒体通信、广播通信</td>
						</tr>
					</tbody>
				</table>
				<p><strong>TCP头部格式</strong></p>
				<p><img src="https://cdn.xiaolincoding.com//mysql/other/format,png-20230309230534096.png"
						style="zoom:50%;" /></p>
				<p>序列号：在建立连接时由计算机生成随机数作为其初始值，通过SYN包传给接收端主机，每发送一次数据，就累加一次该数据字节数的大小。<strong>用来解决网络包乱序问题</strong>。</p>
				<p>确认应答号：指下一次期望收到的数据序列号，发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。<strong>用来解决丢包问题</strong>。</p>
				<p>控制位：用来标识TCP报文是什么类型报文</p>
				<ul>
					<li>ACK，该位为<code>1</code>时，确认应答的字段变为有效。</li>
					<li>RST，该位为<code>1</code>时，表示TCP连接中出现异常必须强制断开连接。</li>
					<li>SYN，该位为<code>1</code>时，表示希望建立连接，并在其序列号的字段进行序列号初始值的设定</li>
					<li>FIN，该位为<code>1</code>时，表示今后不会再有数据发送，希望断开连接。当通信结束希望断开连接时，通信双方的主机之间就可以相互交换FIN为1的TCP段。</li>
				</ul>
				<p><strong>UDP头部格式</strong></p>
				<p><img src="https://cdn.xiaolincoding.com//mysql/other/format,png-20230309230439961.png"
						style="zoom:50%;" /></p>
				<ul>
					<li>目标和源端口，主要告诉UDP协议应该把报文发给哪个进程</li>
					<li>包长度，该字段保存了UDP首部长度跟数据的长度之和</li>
					<li>校验和，校验和是为了提供可靠的UDP首部和数据而设计，防止收到网络传输中受损的UDP包</li>
				</ul>
				<p><strong>TCP与UDP的其他问题</strong></p>
				<table>
					<thead>
						<tr>
							<th></th>
							<th>TCP</th>
							<th>UDP</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>面向字节流与面向报文段</td>
							<td>字节流，用户消息通过TCP协议传输，消息可能会被操作系统分组成多个TCP报文(MSS)，如果接收方的程序不知道<strong>消息边界</strong>，是无法读出有效的用户消息。<strong>我们不能认为一个用户信息对应一个TCP报文，所以TCP是面向字节流的。</strong>
							</td>
							<td>报文段，操作系统不会对消息进行拆分，在组装号UDP头部后交给网络层来处理，所以发出的UDP报文中的数据部分是完整的用户消息，<strong>也就是每个UDP报文就是一个用户消息边界</strong>。
							</td>
						</tr>
						<tr>
							<td>是否能共用一个端口号</td>
							<td>可以使用同一个端口，传输层有两个传输协议分别是TCP、UDP，在内核中是两个完全独立的软件模块（根据IP包头中的协议号字段）</td>
							<td></td>
						</tr>
						<tr>
							<td>粘包问题，一个用户消息的边界</td>
							<td><strong>固定长度消息、特殊字符作为边界、自定义消息结构</strong>(由包头和数据组成，其中包头包是固定大小，且包头里一个字段来说明紧随其后的数据有多大)、<strong>Nagle算法</strong>问题导致的，需要结合应用场景适当关闭该算法
							</td>
							<td>UDP有消息保护边界，不会发生粘包拆包问题</td>
						</tr>
						<tr>
							<td>如何确定一个连接</td>
							<td>四元组(源地址、源端口、目的地址、目的端口)<br />TCP连接最大数 = 客户端IP数 X 客户端的端口数，服务器承受不了（文件描述符限制，系统内存限制）</td>
						</tr>
					</tbody>
				</table>
				<p><strong>粘包问题补充</strong>（TCP粘包，是指发送方发送的若干包数据接收方接收时粘成一包，从接收缓冲区看，后一包数据的头紧接着前一包数据的尾</p>
				<p>什么情况会发生粘包：</p>
				<ul>
					<li>
						<p>由TCP<strong>连接复用</strong>造成的粘包问题。</p>
					</li>
					<li>
						<p>因为TCP默认会使用</p>
						<p>Nagle算法，此算法会导致粘包问题。</p>
						<ul>
							<li>只有上一个分组得到确认，才会发送下一个分组；</li>
							<li>收集多个小分组，在一个确认到来时一起发送。</li>
						</ul>
					</li>
					<li>
						<p><strong>数据包过大</strong>造成的粘包问题。</p>
					</li>
					<li>
						<p>流量控制，<strong>拥塞控制</strong>也可能导致粘包。</p>
					</li>
					<li>
						<p><strong>接收方不及时接收缓冲区的包，造成多个包接收</strong></p>
					</li>
				</ul>
				<h3 id="tcp-">TCP三次握手</h3>
				<pre><code>第一次握手：
    1.客户端将SYN标志位置1
    2.生成一个随机的32位序列号seq=J
    （客户端处于SYN-SENt状态）
第二次握手：
    1.服务端接收客户端的连接：ACK=1
    2.服务器会回发一个确认序号：ack=客户端序列号+数据长度+SYN/FIN按一个字节算
    3.服务端向客户端发起连接请求：SYN=1
    4.服务器会生成一个随机序列号：seq=k
    (服务端处于SYN-RCVD状态)
第三次握手，可以携带数据：
    1.客户端应答服务器的连接请求：ACk=1
    2.客户端回复收到了服务端的数据：ack=服务端的序列号+数据长度+SYN/FIN
    （客户端发送后，处于ESTABLISGED状态，服务端应答后也处于这个状态）
</code></pre>
				<h4 id="tcp-"><strong>TCP握手为什么是三次</strong></h4>
				<ul>
					<li>三次握手才可以阻止重复历史连接的初始化</li>
				</ul>
				<p>如果两次握手，当出现网络阻塞，服务端收到数据包会返回一个SYN、ACK确认信息，客户端收到后发现不是自己期望收到的ACK是历史连接会发送一个RST中止连接，此时服务端没有中间状态给客户端来阻止历史连接，<strong>导致服务端可能建立一个历史连接造成资源浪费。</strong>
				</p>
				<ul>
					<li>三次握手才能同步双方初始序列号</li>
					<li>三次握手才可以避免资源浪费</li>
				</ul>
				<h4 id="-tcp-"><strong>为什么每次建立TCP连接，初始化序列号都要求不一样</strong></h4>
				<ul>
					<li>防止历史报文被下一个相同四元组的连接接收(在这个过程中即使存在TIME_WAIT状态，但是并不是每次连接都是正常关闭)</li>
					<li>为了安全性，防止黑客伪造相同序列号的TCP报文被对方接收</li>
				</ul>
				<p>这里存在一个问题就是绕回问题，根据初始化序列号ISN随机生成算法，ISN=M+F，M为计时器，F为hash算法根据四元组生成的一个随机数值</p>
				<p>由于序列号最高32位，当数字大于32位时，会发生绕回，这意味着无法根据序列号来判断新老数据。</p>
				<p>需要增加TCP时间戳，好处，便于精确计算RTT，另一个是防止序列号回绕</p>
				<p>时间戳也是会回绕，1.增加时间戳大小 2.将一个与时钟频率无关的值作为时间戳</p>
				<h4 id="tcp-">TCP握手丢失问题</h4>
				<p><strong>第一次握手丢失</strong></p>
				<p>重传SYN报文，重传时间每次是上一次的两倍</p>
				<p><strong>第二次握手丢失</strong></p>
				<ul>
					<li>客户端重传SYN报文，最大重传次数<code>tcp_syn_retries</code>内核参数决定</li>
					<li>服务端重传SYN-ACK报文，最大重传次数由<code>tcp_synack_retries</code>内核参数决定，按第一次算</li>
				</ul>
				<p><strong>第三次握手丢失</strong></p>
				<p>服务端重传SYN-ACK报文，客户端不会重传ACK报文</p>
				<h4 id="tcp-">TCP握手优化</h4>
				<p><strong>客户端优化</strong></p>
				<p>适当降低重传次数（tcp_syn_retries参数）</p>
				<p><strong>服务端优化</strong></p>
				<p>1.增大半连接队列，增大tcp_max_syn_backlog，同时还需增大somaxconn与backlog(这个与全连接队列有关)</p>
				<p>2.开启syncookies。服务器根据当前状态计算出一个值，放到已发出的SYN+ACK报文中发出，当客户端返回ACK报文时，去除该值验证，如果合法认为连接建立成功。</p>
				<p>3.适当减少tcp_synack_retries参数</p>
				<p>4.将tcp_abort_on_overflow置为1，server发送一个RST给client，表示废掉这个握手过程和这个连接</p>
				<p><strong>绕过三次握手</strong></p>
				<p>开启TCP Fast Open功能，可以是HTTP请求减少1RTT，Linux下可以通过tcp_fastopen开启这个功能，同时必须保证服务端和客户端支持</p>
				<h4 id="tcp-">TCP握手其他问题</h4>
				<ul>
					<li>SYN报文什么时候会被丢弃</li>
				</ul>
				<p><strong>1.开启tcp_tw_recycle参数，并且在NAT环境下，造成SYN报文丢弃</strong></p>
				<p>tcp_tw_recycle，如果开启这个选项，允许处于TIME_WAIT状态的连接被快速回收。但在使用NAT的网络下是不安全的，如果同时开启recycle与timestamps选项，就会开启PAWS机制，会根据时间戳来判断这个数据包是否过期。但是如果开启NAT网关，不同客户端连接相同服务器，当服务器快速回收TIME_WAIT状态时，<strong>会丢弃timestamp字段较短的一方</strong>
				</p>
				<p><strong>2.TCP两个队列满了（半连接队列和全连接队列），造成SYN丢弃</strong></p>
				<ul>
					<li>已建立连接的TCP，收到SYN会发生什么</li>
				</ul>
				<p><strong>1.客户端的SYN报文里的端口号与历史连接不相同</strong></p>
				<p>四元组不同，服务器以为要建立新连接</p>
				<p>旧连接的处理</p>
				<p>如果服务端发送了数据报给客户端，由于客户端连接已经关闭，此时客户的内核就会回复RST报文，服务端收到后就会释放连接</p>
				<p>如果服务端一直没有发送数据报给客户端，超过一段时间后，TCP保活机制就会启动，检测到客户端没有存活后，服务端就会释放掉该连接</p>
				<p><strong>2.客户端的SYN报文里的端口号与历史连接相同</strong></p>
				<p>处于Established状态的服务端收到客户端SYN报文，由于此时报文初始化序列号不是服务端预期的，则此时服务端会回复一个携带了<strong>正确序列号和确认号的ACK报文（Challenge
						ACK）</strong>，接着，<strong>客户端</strong>收到这个Challenge
					ACK，发现确认号并不是自己期望的，于是就会回<strong>RST报文</strong>，服务端收到后释放掉该连接。</p>
				<ul>
					<li>backlog<ul>
							<li>在Linux内核2.2之前，backlog大小包括半连接状态和全连接状态两种队列大小。</li>
							<li>在Linux内核2.2之后，分离为两个backlog来分别限制半连接（SYN_RCVD状态）队列大小和全连接（ESTABLISHED状态）队列大小。<ul>
									<li>SYN queue 队列长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定，默认为2048。</li>
									<li>Accept queue 队列长度由 /proc/sys/net/core/somaxconn
										和使用listen函数时传入的参数，二者取最小值。默认为128。在Linux内核2.4.25之前，是写死在代码常量 SOMAXCONN
										，在Linux内核2.4.25之后，在配置文件 /proc/sys/net/core/somaxconn 中直接修改，或者在 /etc/sysctl.conf
										中配置 net.core.somaxconn = 128 。</li>
								</ul>
							</li>
						</ul>
					</li>
				</ul>
				<h3 id="tcp-">TCP四次挥手</h3>
				<pre><code>第一次挥手：
    1.客户端FIN标志位置为1，发送FIN报文
    （客户端进入FIN_WAIT_1状态）
第二次挥手：
    1.服务端收到报文后，向客户端发送ACk应答报文
    （服务端进入CLOSE_WAIT状态)
    2.客户端收到ACK报文
    （客户端进入FIN_WAIT_2状态）
第三次挥手：
    1.服务端处理完数据，向客户端发送FIN报文
    （服务端进入LAST_ACK状态）
第四次挥手：
    1.客户端收到FIN状态后，回复一个ACK报文
    (客户端进入TIME_WAIT状态，2MSL后自动进入CLOSE状态)
    2.服务端收到ACk报文
    (服务端进入CLOSE状态)
</code></pre>
				<h4 id="-">为什么挥手需要四次</h4>
				<p>当一方发送<code>FIN</code>报文表示，这方不再发送数据(但可以接收数据)。</p>
				<p>当服务器收到FIN报文同时返回一个ACK报文时，可能服务端还需要处理和发送数据，当数据处理完就会发送FIN报文表示同意关闭连接。</p>
				<p><strong>什么时候四次挥手变成三次挥手</strong></p>
				<p>是否有第三次挥手控制权在被动关闭方</p>
				<p><strong>没有数据发送</strong>并且开启了<strong>TCP延迟确认机制</strong>，那么第二次和第三次挥手就会合并</p>
				<p>TCP延迟确认机制，等待一段时间回复ACK，如果有数据传输那就会立马同数据一起发送ACK，如果收到第二个数据也会立马发送ACK</p>
				<h4 id="tcp-">TCP挥手丢失问题</h4>
				<p><strong>第一次挥手丢失</strong></p>
				<p>客户端重传FIN报文，重传次数<code>tcp_orphan_retries</code>内核参数控制，如果超过重传次数则断开连接进入CLOSE</p>
				<p><strong>第二次挥手丢失</strong></p>
				<p>客户端重传FIN报文(ACK报文是不会重传的)</p>
				<p><strong>第三次挥手丢失</strong></p>
				<p>服务端重传FIN报文</p>
				<p><strong>第四次挥手丢失</strong></p>
				<p>服务端重传FIN报文，如果时间没有超过TIME_WAIT状态的等待时间，就会不断刷新TIME_WAIT状态</p>
				<h4 id="time_wait">TIME_WAIT</h4>
				<h5 id="-time_wait"><strong>为什么需要TIME_WAIT</strong></h5>
				<ul>
					<li>防止历史连接中的数据，被相同四元组的连接错误接收</li>
					<li>保证被动关闭连接的一方，能被正确的关闭</li>
				</ul>
				<p>这个问题也可以用来回答TIME_WAIT时间太短，如果时间太短或没有，主动关闭方收到FIN报文时，会回复一个RST报文(这不是一个优雅的终止方式)</p>
				<h5 id="time_wait-"><strong>TIME_WAIT过多</strong></h5>
				<p>一个是占用系统资源，一个是占用端口资源</p>
				<ul>
					<li>客户端(发起方)的TIME_WAIT状态过多，占满了所有端口资源，那么就无法对目的IP+目的端口都一样的服务器发起连接了，但是被使用的端口，还可以继续对另外一个服务器发起连接的。</li>
					<li>服务端(发起方)的TIME_WAIT状态过多，并不会导致端口资源受限，因为服务端只监听一个端口，而且由于一个四元组唯一确定一个TCP连接，因此理论上服务端可以建立很多连接。</li>
				</ul>
				<h5 id="-time_wait"><strong>优化TIME_WAIT</strong></h5>
				<ul>
					<li>打开 net.ipv4.tcp_tw_reuse和net.ipv4.tcp_timestamps选项</li>
				</ul>
				<p>tcp_tw_reuse功能只能用客户端（发起方），因为开启了该功能，在调用connect()函数时，内核会随机找一个time_wait状态超过1秒的连接给新连接复用，需要TCP时间戳支持。</p>
				<p>tcp_tw_reuse开启是有风险的，对于RST报文的时间戳即使过期了，只要RST报文的序列号在对方的接受窗口内也是能被接受的。因此<strong>快速复用TIME_WAIT状态端口，可能会导致新连接被回绕序列号的RST报文断开</strong>，而如果不跳过TIIME_WAIT状态，而是停留2MSL时长，那么这个RST报文就不会出现在下一个新的连接。
				</p>
				<ul>
					<li>net.ipv4.tcp_max_tw_buckets</li>
				</ul>
				<p>当系统处于TIME_WAIT的连接一旦超过设定的值，系统会将后面的TIME_WAIT连接状态重置</p>
				<ul>
					<li>程序中使用SO_LINGER，应用强制使用RST关闭</li>
				</ul>
				<p>设置调用close关闭连接，如果<code>l_onoff</code>为非0，<code>l_linger</code>为0，那么调用close后，会立刻发送一个<code>RST</code>标志给对端，就会直接跳过四次挥手也就跳过了TIME_WAIT。
				</p>
				<p><strong>如果服务端要避免过多的TIME_WAIT状态的连接，就永远不要主动断开连接，让客户端去断开，由分布在各处的客户端去承受TIME_WAIT</strong></p>
				<h5 id="-time_wait-">服务器出现大量TIME_WAIT状态的原因有哪些</h5>
				<ul>
					<li>HTTP没有使用长连接，排查一下是否客户端和服务端都开启了HTTP Keep-Alive，当客户端未开启，服务端发送完响应会自动关闭</li>
					<li>HTTP长连接超时，如果客户端在完成一个HTTP请求后，在60s内都没有发起新的请求，定时器时间一到，nginx就会出发回调函数来关闭连接，此时服务端就会出现TIME_WAIT状态连接。
					</li>
					<li>HTTP长连接的请求数量达到上限，当HTTP长连接请求数量达到最大，nginx会主动关闭这个长连接。</li>
				</ul>
				<h5 id="-close_wait-">服务器出现大量CLOSE_WAIT状态原因</h5>
				<p>CLOSE_WAIT是被动关闭方才有的状态，如果服务端出现大量CLOSE_WAIT状态连接的时候</p>
				<ul>
					<li>服务器内部业务处理占用了过多时间，都没能处理完业务；或者还有数据需要发送；或者服务器的业务逻辑有问题，<strong>没有执行close()方法</strong></li>
					<li>服务器的父进程派生出子进程，<strong>子进程继承了socket，收到FIN的时候子进程处理但父进程没有处理该信号，导致socket的引用不为0无法回收</strong></li>
				</ul>
				<p>处理方法：</p>
				<ul>
					<li>停止应用程序</li>
					<li>修改程序里的bug</li>
				</ul>
				<h4 id="tcp-">TCP挥手优化</h4>
				<p><strong>主动方优化</strong></p>
				<p>1.适当降低重传FIN报文次数，调低tcp_orphan_retries参数(第一次挥手)</p>
				<p>2.当主动方收到ACK报文后，连接就进入FIN_WAIT2状态，根据关闭关闭方式不同，优化方式也不同：</p>
				<ul>
					<li>close函数，那么它就是孤儿连接(完全断开连接，无法发送数据无法接受数据)。如果tcp_fin_timeout秒内没有收到对方的FIN报文，连接就直接关闭，同时为了应对孤儿连接占用太多资源，tcp_max_orphans定义了最大孤儿连接数量，超过时便直接释放。
					</li>
					<li>反之shutdown函数，则不收此参数限制。</li>
				</ul>
				<p>3.调整TIME_WAIT状态上限个数，调整tcp_max_tw_buckets</p>
				<p>4.当TIME_WAIT状态过多时，可以通过设置tcp_tw_reuse和tcp_timestamps为1，将TIME_WAIT状态端口复用作为新客户端的新链接，注意该参数只适用于客户端</p>
				<p><strong>被动方优化</strong></p>
				<p>1.被动方在回复ACK后就进入CLOSE_WAIT状态，等待进程调用close函数关闭连接。因此，出现大量CLOSE_WAIT状态的连接时，应当从应用程序中找到问题。</p>
				<p>2.调整FIN报文重传次数，调整tcp_orphan_retries参数</p>
				<h4 id="tcp-">TCP挥手其他问题</h4>
				<ul>
					<li>四次挥手中收到乱序的FIN包会如何处理</li>
				</ul>
				<p>在FIN_WAIT状态时，如果收到乱序的FIN报文，那么就会被加入到乱序队列，并不会进入到TIME_WAIT状态。</p>
				<p>等在此收到前被网络延迟的数据包时，会判断乱序队列有没有数据，然后会检测乱序队列是否有可用的数据，如果能在乱序队列中找到当前报文的序列号保持的顺序的报文，就会看该报文是否有FIN编排制，如果发现FIN标志，这是才会进入TIME_WAIT状态。
				</p>
				<ul>
					<li>在TIME_WAIT状态的TCP连接，收到SYN后发生什么</li>
				</ul>
				<p><strong>收到合法SYN</strong>（客户端SYN序列号与时间戳都要大于服务端期望收到的序列号与最后收到报文的时间戳，如果没有开启TCP时间戳就不需要比较时间戳）</p>
				<p>就会重用此四元组，跳过2MSL而转变为SYN_RECV状态，接着能进行建立连接过程</p>
				<p><strong>收到非法SYN</strong>（与合法SYN相反）</p>
				<p>服务端会回复一个第四次挥手的ACK报文，客户端收到后，发现并不是自己期望收到的确认号(ack
					num)，就会回RST报文给服务端，如果tcp_rfc1337为0，则提前结束TIME_WAIT，如果为1，则丢掉该RST报文</p>
				<h3 id="socket">Socket</h3>
				<h4 id="socket-">socket流程</h4>
				<center class="half">
					<img src="https://cdn.xiaolincoding.com//mysql/other/format,png-20230309230545997.png"
						style="zoom:40%;" />
					<img src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/TCP-%E5%8D%8A%E8%BF%9E%E6%8E%A5%E5%92%8C%E5%85%A8%E8%BF%9E%E6%8E%A5/3.jpg"
						style="zoom:40%;" />
				</center>

				<pre><code>1.服务端和客户端初始化socket，得到文件描述符
2.服务端调用bind，将socket绑定在指定的IP地址和端口（可以叫做socket命名）
3.服务端listen，进行监听
4.服务端调用accept，等待客户端连接
5.客户端调用connect，向服务端地址和端口发起连接请求
6.服务端accept返回用于传输的socket的文件描述符
7.客户端调用write写入数据，服务端调用read读取数据
8.客户端断开连接时，会调用close，那么服务端read读取数据的时候，就会读取到EOF，待处理完数据后，服务端调用close，表示连接关闭
</code></pre>
				<p><strong>connect函数</strong>，该函数功能为客户端主动连接服务器，建立连接是通过三次握手，而这个连接的过程是由内核完成，不是这个函数完成，这个函数的作用仅仅通知Linux内核，让Linux内核自动完成TCP握手连接，最后把链接结构返回给中国函数的返回值。<strong>(函数默认阻塞，直至握手完成或者失败)</strong>
				</p>
				<p><strong>listen函数</strong>，listen函数不会阻塞，它主要工作就是，将套接字和套接字对应的连接队列长度告诉Linux内核，然后，listen函数结束。<strong>(backlog)</strong>
				</p>
				<p><strong>accept函数</strong>，从established状态的连接队列头部取出一个已完成的连接，如果这个队列没有已经完成的连接，accept函数就会阻塞，知道取出队列中已经完成的用户连接。
				</p>
				<ul>
					<li><strong>半连接队列(SYN队列)</strong>，服务端<strong>第一次握手</strong>后，会将sock加入到这个队列中，队列内的sock都处于syn_recv状态。<strong>（哈希表,需要找到对应端口IP的连接）</strong>
					</li>
					<li><strong>全连接队列(ACCEPT队列)</strong>，在服务端收到<strong>第三次握手</strong>后，会将半连接队列的sock取出，放到全连接队列。队列里的sock都处于ESTABLISHED状态。这里面的连接，就等着服务端执行accept()后被取出了。<strong>（链表,只需要取出头部就行）</strong>
					</li>
				</ul>
				<h4 id="-close-">客户端连接服务端过程，客户端调用close，断开流程</h4>
				<center class="half">
					<img src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4/%E7%BD%91%E7%BB%9C/socket%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B.drawio.png"
						style="zoom:50%;" />
					<img src="https://cdn.xiaolincoding.com//mysql/other/format,png-20230309230538308.png"
						style="zoom:50%;" />
				</center>

				<h4 id="-">握手过程中的问题</h4>
				<ul>
					<li>半连接队列满了</li>
				</ul>
				<p>一般是丢弃</p>
				<p>解决办法，增大TCP半连接队列，增大/proc/sys/net/ipv4/tcp_max_syn_backlog</p>
				<p>开启tcp_syncookies，可以在不使用SYN半连接队列的情况下建立连接</p>
				<ul>
					<li>全连接队列满了</li>
				</ul>
				<p>tcp_abort_on_overflow = 0, 会丢弃第三次握手的ack包，并重传第二次握手，重传超过一定次数还会将对应半连接队列的连接删除</p>
				<p>tcp_abort_on_overflow = 1, 发送RST给客户端(现象表现根服务端端口未监听一样)</p>
				<p>解决办法，增大TCP全连接队列，增大listen()函数中的backlog和somaxconn</p>
				<ul>
					<li>抵御SYN攻击</li>
				</ul>
				<p>1.增大半连接队列</p>
				<p>2.开启tcp_syncookies功能</p>
				<p>3.减少SYN+ACK重传次数</p>
				<ul>
					<li>关闭连接函数</li>
				</ul>
				<p>tcp_fin_timeout 无法控制shutdown关闭连接</p>
				<ul>
					<li>没有accept，能建立TCP连接吗</li>
				</ul>
				<p>可以的，accept系统调用并不参与TCP三次握手过程，它只负责从TCP全连接队列中取出一个已经建立连接的socket，用户层通过accept系统调用拿到已经建立连接的socket，就可以对该socket进行读写操作。
				</p>
				<ul>
					<li>没有listen，能建立TCP连接吗</li>
				</ul>
				<p>如果发给服务端，服务端会发送一个RST报文给客户端，因为找不到监听的socket。</p>
				<p>可以的，客户端可以自己连自己形成连接(TCP自连接)，也可以两个客户端同时向对方发出请求建立连接(TCP同时打开)，这两个情况都有个共同点，没有服务端参与，也就没有listen建立TCP连接</p>
				<h3 id="tcp-">TCP可靠性机制</h3>
				<p>TCP报文发送时机取决于发送窗口、拥塞窗口以及当前发送缓冲区的大小等条件</p>
				<h4 id="-">重传机制</h4>
				<p>超时重传机制，接收方收到报文回复确认报文，发送方一段时间后没有收到确认报文就会重传自己的报文。发送方的报文丢失也会重传自己的报文，本质原因是由于没有接受到接收方发送的确认报文。</p>
				<p>快速重传，不以时间为驱动，以数据驱动重传。存在问题，重传的时候，不知道是重传一个还是重传所有的问题。<strong>选择三次ACK</strong>，<strong>这表明接收方已经接收到了后续的数据包，只是某个数据包丢失了。这可能是由于网络发生了拥塞，所以触发快速重传，减少拥塞对网络性能的影响</strong>。收到2次可能只是网络延迟或者乱序，不一定要重传。
				</p>
				<p>SACK机制，只重传丢失数据(将已收到的数据信息发送给发送方)</p>
				<p>D-SACK机制，主要使用SACk来告诉发送方有哪些数据被重复接收</p>
				<h4 id="-">滑动窗口</h4>
				<p>TCP每发送一个数据都得应答一次，如果数据包往返的时间越长，通信的效率就越低，使用引入窗口，窗口大小就是指<strong>无需等待确认应答，而可以继续发送数据的最大值</strong>。</p>
				<p>窗口大小，TCP头里有个字段叫window，就是窗口大小。这个字段是接收端告诉发送端之间还有多少缓冲区可以接收数据，于是发送端就可以根据接收端处理能力来发送数据，所以通常窗口大小是由接收方的窗口大小决定的。
				</p>
				<p><strong>TCP利用滑动窗口实现流量控制的机制，</strong>TCP
					中采用滑动窗口来进行传输控制，<strong>滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据</strong>。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0
					时，发送方一般不能再发送数据报，但有两种情况除外，一种情况是可以发送紧急数据。</p>
				<pre><code>例如，允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。
</code></pre>
				<h4 id="-">流量控制</h4>
				<p>TCP提供一种机制可以让发送方根据接收方的实际能力控制发送的数据量这就是所谓的流量控制。(当接收方来不及处理发送方的数据，能通过滑动窗口提示发送降低发送速率，防止包丢失)</p>
				<ul>
					<li>目的是接收方通过TCP头窗口字段告知发送方本方可接收的最大数据量，用以解决发送速率过快导致接收方不能接收的问题。所以流量控制是点对点控制。</li>
					<li>TCP是双工协议，双方可以同时通信，所以发送方接收方各自维护一个发送窗和接收窗。<ul>
							<li>发送窗：用来限制发送方可以发送的数据大小，其中发送窗口的大小由接收端返回的TCP报文段中窗口字段来控制，接收方通过此字段告知发送方自己的缓冲（受系统、硬件等限制）大小。
							</li>
							<li>接收窗：用来标记可以接收的数据大小。</li>
						</ul>
					</li>
					<li>TCP是流数据，发送出去的数据流可以被分为以下四部分：已发送且被确认部分 | 已发送未被确认部分 | 未发送但可发送部分 | 不可发送部分，其中发送窗 = 已发送未确认部分 +
						未发但可发送部分。接收到的数据流可分为：已接收 | 未接收但准备接收 | 未接收不准备接收。接收窗 = 未接收但准备接收部分。</li>
					<li>发送窗内数据只有当接收到接收端某段发送数据的ACK响应时才移动发送窗，左边缘紧贴刚被确认的数据。接收窗也只有接收到数据且最左侧连续时才移动接收窗口。</li>
				</ul>
				<p>TCP规定是不允许同时减少缓冲又收缩窗口的，而是采用先收缩窗口，过段时间再减少缓存，可以就可以避免丢包。</p>
				<h4 id="-">拥塞控制</h4>
				<p>避免发送方的数据填满整个网络。<strong>因为在网络出现拥塞时，如果继续发送大量数据包，可能会导致数据时延、丢失等，这是TCP就会重传数据，一旦重传就会导致网络负担更加严重，陷入恶性循环。</strong>
				</p>
				<p>拥塞窗口cwnd，接收窗口rwnd，发送窗口swnd=min(cwnd,rwnd)</p>
				<p>cwnd变化规律，1.只要网络没有出现拥塞cwnd就会增大 2.网络出现拥塞cwnd减少</p>
				<p><strong>一般发生超时重传，就会被认为网络出现了拥塞</strong></p>
				<ul>
					<li>
						<p>慢启动，当发送方没收到一个ACK，拥塞窗口cwnd大小加1</p>
					</li>
					<li>
						<p>拥塞避免，当拥塞窗口cwnd超过慢启动门限ssthresh就会进入拥塞避免算法（每当收到一个ACK时，cwnd增加1/cwnd)</p>
					</li>
					<li>拥塞发生，超时重传（1.ssthresh设为cwnd/2 2.cwnd重置为1），快速重传（1.cwnd /= 2，设置为原来的一半 2.ssthresh = cwnd 3.进入快速算法）
					</li>
					<li>快速恢复，cwnd = cwnd / 2, ssthresh = cwnd</li>
				</ul>
				<p>1.拥塞窗口 cwnd = ssthresh + 3</p>
				<p>2.重传丢失数据包</p>
				<p>3.如果再收到重复的ACK，那么cwnd增加1</p>
				<p>4.如果收到杏树的ACK，把cwnd设置为第一步中的ssthresh的值，原因是该ACK确认了新数据，说明从duplicated
					ACK时数据都已收到，该恢复过程已经结束，可以回到恢复之前的状态，也即再次进入拥塞状态。</p>
				<h5 id="-">区分流量控制和拥塞控制</h5>
				<ul>
					<li>流量控制属于<strong>通信双方协商</strong>；拥塞控制涉及通信链路全局。</li>
					<li>流量控制需要通信双方各维护一个发送窗、一个接收窗，对任意一方，接收窗大小由自身决定，发送窗大小由接收方响应的TCP报文段中窗口值确定；</li>
					<li>拥塞控制的拥塞窗口大小变化由试探性发送一定数据量数据探查网络状况后而自适应调整。</li>
					<li>实际最终发送窗口 = min{流控发送窗口，拥塞窗口}。</li>
				</ul>
				<h4 id="-">数据校验</h4>
				<p>TCP报文头有校验和，用于校验报文是否损坏</p>
				<h4 id="-">数据合理分片和排序</h4>
				<p>TCP会按最大传输单元(MTU)合理分片，接收方会缓存未按序到达的数据，重新排序后交给应用层。</p>
				<p>而UDP，IP数据报大于1500字节，大于MTU。这个时候发送方IP层分片，把数据报分成若干片，是的每一片都小于MTU。而接收方IP层需要进行数据报的重组。由于UDP特性，某一片数据丢失时，接收方便无法重组数据报，导致丢弃整个UDP数据报。
				</p>
				<h4 id="tcp-">TCP传输数据性能提升</h4>
				<p>TCP连接是由内核维护，内核会为每个连接建立内存缓冲区：</p>
				<ul>
					<li>如果连接的内存配置过小，就无法充分使用网络宽带，TCP传输效率就会降低</li>
					<li>如果连接的内存配置过大，很容易把服务器资源耗尽，这样就会导致新连接无法建立</li>
				</ul>
				<p>滑动窗口定义了网络中飞行报文的最大字节数，当它超过带宽时延积时，网络过载，就会发生丢包。而当它小于带宽时延积时，就无法充分利用网络宽带</p>
				<p>传输数据提升：</p>
				<ul>
					<li>扩大窗口大小，tcp_window_scaling</li>
					<li>调整发送缓冲区范围，tcp_wmem</li>
					<li>调整接收缓冲区范围，tcp_rmem</li>
					<li>打开接收缓冲区动态调节，tcp_moderate_rcvbuf</li>
					<li>调整内存范围，tcp_mem</li>
				</ul>
				<h3 id="-">端口</h3>
				<h4 id="-">客户端端口选择流程</h4>
				<p><img src="https://cdn.xiaolincoding.com/gh/xiaolincoder/network/port/%E7%AB%AF%E5%8F%A3%E9%80%89%E6%8B%A9.jpg"
						style="zoom: 40%;" /></p>
				<ul>
					<li>多个TCP服务进程可以同时绑定同一个端口吗？</li>
				</ul>
				<p>绑定IP地址和端口都相同，那么执行bind()就会出错，错误是“Address already in use”</p>
				<p>0.0.0.0地址比较特殊，绑定0.0.0.0意味着绑定主机上所有IP地址</p>
				<ul>
					<li>重启TCP服务进程时，为什么会出现“Address in use”的报错信息？又改怎么避免？</li>
				</ul>
				<p>TCP服务进程不能很快地重启，而是要过一会儿才能重启成功，因为当我们重启TCP服务进程时，意味着通过服务器发起了关闭连接操作，于是就会经过四次挥手，对于主动方，会在TIME_WAIT停留一段时间，TIME_WAIT状态的连接使用IP+PORT仍然被认为是一个有效的IP+PORT组合。
				</p>
				<p>避免，开启SO_REUSEADDR选项。</p>
				<ul>
					<li>客户端的端口可以重复使用吗？</li>
				</ul>
				<p>四元组，可以</p>
				<ul>
					<li>多个客户端可以bind同一个端口吗？</li>
				</ul>
				<p>如果多个客户端同时绑定的IP地址和端口号都相同那么就会出错</p>
				<p>客户端一般用connect函数，因为客户端端口通常没什么意义</p>
				<ul>
					<li>客户端TCP连接TIME_WAIT状态过多，会导致端口资源耗尽而无法建立新的连接吗？</li>
				</ul>
				<p>只要客户端连接的服务器不同，端口资源可以重复使用的</p>
				<ul>
					<li>端口号上限</li>
				</ul>
				<p>65536 = 2 ^ 16,由于0到1023是知名服务端口，所以还要少1024个端口。</p>
				<p>对于服务器来说，开的端口号与65536无关，只受限于Linux可以打开的文件数量，可以通过MaxYserPort来进行配置</p>
				<h3 id="tcp-">TCP保活机制</h3>
				<h4 id="tcp-keepalive-http-keep-alive">TCP Keepalive 和 HTTP Keep-Alive</h4>
				<ol>
					<li>Keep-Alive，称为HTTP长连接，是应用层(用户态)实现的。使用一个TCP连接来发送和接收多个HTTP请求/应答</li>
					<li>Keepalive，TCP保活机制，由TCP层(内核态)实现。TCP保活机制可以在双方没有数据交互的情况下，通过探测报文，来确定对方的TCP连接是否存活，这个工作在内核完成。（socket接口设置SO_KEEPALIVE）
					</li>
				</ol>
				<p><strong>具体情况：</strong></p>
				<p>1.如果对端程序时正常工作。当TCP保活的探测报文发送给对端，对端会正常响应，同时<strong>保活时间重置</strong>，等待下一个TCP保活时间到来。</p>
				<p>2.对端主机宕机并重启。当TCP保活的探测报文发送给对端后，对端是可以响应的，但由于没有该连接的有效信息，会产生一个RST报文，这样就会发现TCP连接已经被重置。</p>
				<p>3.如果对端主机宕机(<strong>不是进程崩溃，进程崩溃后操作系统会回收资源，之后会发送FIN报文。而主机宕机是无法感知的，所以需要保活机制来探测对方是不是发生了主机宕机</strong>),或对端由于其他原因导致报文不可达。当TCP保活的探测报文发送给对端，没有响应，连续几次，达到保活探测次数，TCP会报告TCP连接已经死亡。
				</p>
				<h4 id="-">已建立连接，客户端出现问题</h4>
				<p>1.<strong>开启保活机制</strong>，三种情况如上，1.客户端程序正常工作 2.客户端主机宕机并重启 3.客户端主机宕机</p>
				<p>2.<strong>未开启保活机制</strong></p>
				<ul>
					<li>第一种进程崩溃，内核会进行四次挥手。</li>
					<li>第二种客户端宕机立即重启<ul>
							<li>如果客户端主机上没有进程绑定该TCP报文目标端口号，客户端内核就会回复RST报文，<strong>重置TCP连接</strong>。</li>
							<li>如果主机上该进程绑定TCP报文的目标端口号，由于主机重启，<strong>之前TCP连接的数据结构已经丢失</strong>，客户端内核里协议栈找不到该TCP连接的socket结构体，于是会回复RST报文，<strong>重置该TCP连接</strong>。
							</li>
						</ul>
					</li>
					<li>第三种客户端宕机未重启，<ul>
							<li><strong>如果服务端发送数据</strong>，由于客户端不存在，服务端接收不到客户端响应报文，服务端会触发重传，当次数到达一定阈值，就会断开TCP连接</li>
							<li><strong>如果服务端没有发送数据</strong>，由于没有开启保活机制，服务端TCP连接会一直存在，并一直保持ESTABLISHED状态（如果开启保活机制，会探测对方）
							</li>
						</ul>
					</li>
				</ul>
				<h4 id="-">已建立连接，服务端出现问题</h4>
				<p>TCP连接信息是由内核维护的，所以服务端的进程崩溃后，内核需要回收该进程的所有TCP连接资源，于是内核会发送第一次挥手FIN报文，后续的挥手过程都是在内核完成，并不需要进程参与，所以即使服务端进程退出了，还是能与客户端完成TCP四次挥手
				</p>
				<h4 id="-tcp-">拔掉网线后，原本的TCP连接还存在吗</h4>
				<p><strong>TCP连接在Linux内核中是一个名为struct socket 的结构体，该结构体的内容包含TCP连接的状态等信息。</strong></p>
				<ul>
					<li>有数据传输(会触发重传)<ul>
							<li>重传过程中，正好把网线插回去。客户端就会回复ACK响应报文，然后无事发生。</li>
							<li>一直没有将网线插回去。超过重传次数，服务端TCP连接就会断开，等客户端将网线插回去，服务端已经断开连接，如果客户端此时向服务端发送数据，因为服务端已经没有与客户端相同的四元组TCP连接了，服务端内核就会回复RST报文，客户端收到就会释放该TCP连接。
							</li>
						</ul>
					</li>
					<li>无数据传输，就看有没有开启保活机制。</li>
				</ul>
				<h4 id="-">其他问题</h4>
				<ul>
					<li>TCP连接，一端断电和进程崩溃有什么区别</li>
				</ul>
				<p>断电相当于主机宕机，进程崩溃就是进程崩溃</p>
				<ul>
					<li>保活机制检测时间有点长，我们可以自己在应用层实现一个心跳机制</li>
				</ul>
				<p>比如web服务软件一般会提供keepalive_time参数，用来指定HTTP长连接超时时间。如果设置长连接超时时间是60秒，web服务器就会启动一个定时器，如果客户端在完成一个HTTP请求后，在60秒后没有再发起新请求，定时时间一到，<strong>就会触发回调函数来释放该连接</strong>。
				</p>
				<h3 id="-">其他问题</h3>
				<h4 id="tcp-"><strong>TCP协议有什么缺陷</strong></h4>
				<ol>
					<li><strong>升级TCP的工作困难。</strong>内核升级很麻烦，涉及到底层软件更新和运行库更新，许多服务程序需要回归测试是否兼容新的内核版本，还有许多TCP协议新特性需要客户端与服务端同时支持才能生效。
					</li>
					<li><strong>TCP建立连接的延迟。</strong>先建立三次握手，如果是HTTPS还需要四次TLS握手，TCP Fast
						Open可以减少1RTT握手时间，但是由于TCP是内核实现握手，TLS是应用层实现握手，所以TLS是无法对TCP进行头部加密的，意味着TCP序列号是明文传输，存在安全问题，比如RST报文攻击（需要获取序列号）、截取客户端报文。
					</li>
					<li><strong>TCP存在队头阻塞问题。</strong>TCP层必须保证收到的字节数据完整且有序，当内核中的TCP数据不连续，接收方的应用层就无法从内核中读取到。</li>
					<li><strong>网络迁移需要重新建立TCP连接。</strong>IP换了四元组变了，需要重新连接</li>
				</ol>
				<h4 id="-ip-tcp-mss">有IP层分片，为什么TCP层还需要MSS</h4>
				<p>因为当一个IP分片丢失，整个IP报文的所有分片都得重传。因为IP层本身没有超时重传机制，它由传输层的TCP来负责超时和重传。当某一个IP分片丢失后，接收方的IP层就无法组装成一个完整的TCP报文，也就无法将数据报文送达TCP层，所以接收方不会响应ACK给发送方，因为发送方迟迟收不到ACK确认报文，所以会出发超时重传，就会重发整个TCP报文。由此可见IP层进行分片传输是非常没有效率的。
				</p>
				<h4 id="close-shutdown">close 与 shutdown</h4>
				<p>调用close函数意味着完全断开连接，不仅无法传输数据，而且不能发送数据，此时调用close函数的一方的连接叫做孤儿连接。</p>
				<p>而shutdown函数相交close函数更加优雅，他可以控制只关闭一个方向的连接。SHUT_RD(0)，关闭连接读方向。SHUT_WR(1)，关闭连接写方向。SHUT_RDWR(2)，关闭套接字的读写方向。
				</p>
				<h4 id="tcptwreuse-">tcptwreuse为什么默认关闭</h4>
				<p>1.历史RST报文可能会终止后面相同四元组的连接，因为PAWS检查到即使RST过期也不会丢弃。</p>
				<p>2.如果第四次挥手的ACK报文丢失了，有可能被动关闭连接的一方不能被正常关闭。<strong>如果此时TIME_WAIT被快速复用</strong>，处于last_ack状态的服务端收到客户端的SYN，还会回复<strong>一个确认号与服务端上次发送ACK报文的确认号一样的ACK报文，叫challenge
						ACK</strong>，而处于syn_sent状态的客户端收到服务端的Challenge ACK后，发现不是自己期望的确认号，就会回复RST报文，服务端收到后会断开连接。</p>
				<h4 id="https-tls-tcp-">HTTPS中TLS和TCP能同时握手吗</h4>
				<p>HTTPS是先进行TCP三次握手，再进行TLSv1.2四次握手。</p>
				<p>HTTPS中TLS握手过程可以同时进行三次握手，需要两个前提：</p>
				<ul>
					<li>客户端和服务端都开启了TCP Fast Open 功能，且TLS版本是1.3</li>
					<li>客户端和服务端已经完成过一次通信</li>
				</ul>
				<p>TCP Fast Open可以让第二次握手减少1个RTT</p>
				<p>TLS1.3握手只需要1RTT，会话回复只需要0RTT</p>
				<h4 id="-tcp-">用了TCP协议，数据一定不会丢吗</h4>
				<p><strong>TCP协议保证的可靠性是传输层的可靠性，也就是说，只能保证数据从机器A传输层可靠地发送到机器B传输层。</strong>并不能保证应用层数据不丢包，当聊天软件将数据从TCP接收缓冲区读出来，在读出来的那一刻，手机由于内存不足或者其他原因，导致闪退，消息就丢了。
				</p>
				<p>可能产生丢包：</p>
				<p>1.建立连接时，当全连接或者半连接队列满了，就会发生丢包。</p>
				<p>2.流量控制丢包，当发送数据过快，留空队列长度有不够大，容易出现丢包</p>
				<p>3.网卡丢包，网卡和它的驱动导致丢包场景也比较常见，比如网线质量差、接触不良。RingBuffer过小导致丢包，接收数据时，会将数据暂存在RingBuffer接收缓冲区然后等着内核触发软中断慢慢收走。网卡性能不足，网卡作为硬件，传输速度是有上限的。
				</p>
				<p>4.接收缓冲区丢包，当接收缓冲区满了，就是所谓零窗口，一般这种情况会告诉发送端窗口大小为0，但是如果此时还有数据传来，就会发生丢包。</p>
				<p>5.两端之间网络丢包，两端之间那么长的一条链路都属于外部网络，这中间有各种路由器和交换机还有光缆，丢包进发生。</p>
				<h4 id="csrf-">CSRF攻击</h4>
				<ul>
					<li><strong>解释</strong>：跨站点请求伪造，指攻击者通过跨站请求，以合法的用户的身份进行非法操作。<strong>意思是攻击者盗用你的身份，以你的名义向第三方网站发送恶意请求。</strong>
					</li>
					<li>
						<p>CRSF能做的事情：包括利用你的身份发邮件，发短信，进行交易转账，甚至盗取账号信息。</p>
					</li>
					<li>
						<p><strong>怎么防范：</strong></p>
						<ul>
							<li><strong>安全框架</strong>，例如Spring Security。
								<strong>token机制</strong>。在HTTP请求中进行token验证，如果请求中没有token或者token内容不正确，则认为CSRF攻击而拒绝该请求。
								<ul>
									<li>1）验证请求来源地址； </li>
									<li>2）关键操作添加验证码； </li>
									<li>3）在请求地址添加 token 并验证。</li>
								</ul>
							</li>
							<li><strong>验证码</strong>。通常情况下，验证码能够很好的遏制CSRF攻击，但是很多情况下，出于用户体验考虑，验证码只能作为一种辅助手段，而不是最主要的解决方案。
							</li>
							<li><strong>referer识别</strong>。在HTTP
								Header中有一个字段Referer，它记录了HTTP请求的来源地址。如果Referer是其他网站，就有可能是CSRF攻击，则拒绝该请求。但是，服务器并非都能取到Referer。很多用户出于隐私保护的考虑，限制了Referer的发送。在某些情况下，浏览器也不会发送Referer，例如HTTPS跳转到HTTP。
							</li>
						</ul>
					</li>
				</ul>
				<h4 id="-">文件上传漏洞</h4>
				<p><strong>文件上传漏洞，指的是用户上传一个可执行的脚本文件，并通过此脚本文件获得了执行服务端命令的能力。</strong>
					许多第三方框架、服务，都曾经被爆出文件上传漏洞，比如很早之前的Struts2，以及富文本编辑器等等，可被攻击者上传恶意代码，有可能服务端就被人黑了。</p>
				<p><strong>如何防范文件上传漏洞</strong></p>
				<p>文件上传的目录设置为不可执行。</p>
				<ul>
					<li>1）判断文件类型。在判断文件类型的时候，可以结合使用MIME
						Type，后缀检查等方式。因为对于上传文件，不能简单地通过后缀名称来判断文件的类型，因为攻击者可以将可执行文件的后缀名称改为图片或其他后缀类型，诱导用户执行。</li>
					<li>2）对上传的文件类型进行白名单校验，只允许上传可靠类型。</li>
					<li>3）上传的文件需要进行重新命名，使攻击者无法猜想上传文件的访问路径，将极大地增加攻击成本，同时向shell.php.rar.ara这种文件，因为重命名而无法成功实施攻击。</li>
					<li>4）限制上传文件的大小。</li>
					<li>5）单独设置文件服务器的域名。</li>
				</ul>
				<h3 id="-udp-">如何基于UDP实现可靠传输</h3>
				<p>UDP配合QUIC进行可靠传输</p>
				<p>QUIC也需要三次握手建立连接，主要目的是为了协商连接ID。协商出连接ID，后序传输时，双方只需要固定住连接ID，从而实现连接迁移功能。</p>
				<ul>
					<li>可靠传输</li>
				</ul>
				<p>Packet Header ，细分为Long Packet Header 与 Short Packet Header。Short Packet Header中的Packet
					Number是每个报文独一无二的编号，它是严格递增的，也就说Packect N丢失，重传的Packet Number
					N的值更大。(<strong>可以更加精准计算RTT，可以支持乱序确认</strong>)</p>
				<p>QUIC Frame Header，Stream
					ID（区分不同的HTTP信息），Offset（类似于TCP协议中的Seq序号，保证数据顺序性和可靠性）、Length（指明Frame数据长度）。如果丢失的数据报和重传的数据报Stream
					ID与Offset都一直说明这两个数据包内容一致。</p>
				<p>总之，QUIC通过单向递增的Packet Number，配合Stream ID与Offset字段信息，可以支持乱序确认而不影响数据包的正确组装。</p>
				<ul>
					<li>队头阻塞</li>
				</ul>
				<p>QUIC给<strong>每一个Stream都分配了一个独立的滑动窗口</strong>，这样使得一个连接上的多个Stream之间没有依赖关系，都是相互独立的，各自控制的滑动窗口。</p>
				<ul>
					<li>流量控制</li>
				</ul>
				<p>方式：</p>
				<p>1.通过window_update帧告诉对端主机可以接收的字节数，这样发送方就不会发送超过这个数量的数据。</p>
				<p>2.通过BlockFrame告诉对端由于流量控制被阻塞了，无法发送数据。</p>
				<p>QUIC的每个Stream都有各自的滑动窗口，不同Stream互相独立，队头的Stream A被阻塞德厚，不妨碍StreamB,C的读取。</p>
				<p>QUIC实现了，<strong>Stream级别的流量控制</strong>(每个Stream都有独立的滑动窗口，所以每个Stream都可以做流量控制)、<strong>Connection流量控制</strong>(限制连接中所有Stream相加起来的总字节数)
				</p>
				<ul>
					<li>拥塞控制</li>
				</ul>
				<p>QUIC可以随浏览器更新，QUIC的拥塞控制算法就可以有较快的迭代速度。因为QUIC处于应用层，可以针对不同的应用设置不同的拥塞控制算法。</p>
				<ul>
					<li>更快连接</li>
				</ul>
				<p>QUIC内部包含TLS，它在自己帧会携带TLS里的记录，再加上QUIC使用的是TLS1.3，因此仅需1个RTT就可以同时完成建立连接与密钥协商，甚至在第二次连接的时候，应用数据包可以和QUIC握手信息（连接信息+TLS信息）一起发送，达到0-RTT的效果
				</p>
				<ul>
					<li>迁移连接</li>
				</ul>
				<p>QUIC通过<strong>连接ID</strong>来标记通信的两个端点，客户端和服务器可以各自选择一组ID来标识自己，即使移动设备网络变化导致IP变化，只要仍保有<strong>上下文信息(连接ID、TLS密钥)</strong>，就可以无缝复用原连接，消除重连成本。
				</p>
				<h4 id="-">其它问题</h4>
				<ul>
					<li>在进行UDP编程的时候，一次发送打算bytes好？</li>
				</ul>
				<p>以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的物理特性决定的.这个1500字节被称为链路层的MTU(最大传输单元).但这并不是指链路层的长度被限制在1500字节,其实这这个MTU指的是链路层的数据区.并不包括链路层的首部和尾部的18个字节.
				</p>
				<p>所以,事实上,这个<strong>1500字节</strong>就是网络层IP数据报的长度限制。因为<strong>IP数据报的首部为20字节</strong>,所以IP数据报的数据区长度最大为1480字节.而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的.<strong>又因为UDP数据报的首部8字节</strong>,所以UDP数据报的数据区<strong>最大长度为1472字节</strong>.这个1472字节就是我们可以使用的字节数。
				</p>
				<p>进行Internet编程时则不同,因为Internet上的路由器可能会将MTU设为不同的值.
					如果我们假定MTU为1500来发送数据的,而途经的某个网络的MTU值小于1500字节,那么系统将会使用一系列的机 制来调整MTU值,使数据报能够顺利到达目的地,这样就会做许多不必要的操作.</p>
				<p>鉴于Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时. 最好将UDP的数据长度控件在<strong>548字节(576-8-20)</strong>以内
				</p>
				<h2 id="ip">IP</h2>
				<h3 id="ip-mac-">IP(网络层)和MAC(数据链路层)</h3>
				<ul>
					<li>MAC的作用是实现直连的两个设备之间通信(可以理解为主机到路由器，路由器到路由器)</li>
					<li>IP负责没有直连的两个网络之间进行通信传输(可以理解为两个主机之间通信)</li>
				</ul>
				<p>所以说，源IP地址和目标IP地址在传输过程中是不会变化的（前提没有使用NAT网络），只有源MAC地址和目标MAC一直在变化。</p>
				<h3 id="ipv4-ipv6">IPv4与IPv6</h3>
				<h4 id="-">网络地址分类</h4>
				<p>IP地址是根据网卡来配置的，像服务器、路由器等设备都是有2个以上的网卡，也就是他们会有2个以上的IP地址。</p>
				<p><img src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/IP/7.jpg"
						style="zoom: 70%;" /></p>
				<p><strong>网络号与主机号的作用：</strong></p>
				<p>两台计算机通讯，首先要判断是否处于同一个广播域内，即网络地址是否相同。如果网络地址相同，表明接收方在本网络上，那么可以把数据包直接发送给目标主机。路由器寻址也是这样工作。</p>
				<ul>
					<li><strong>A、B、C分类</strong>：对于这三类主要分为两部分，分别是<strong>网络号和主机号</strong></li>
				</ul>
				<table>
					<thead>
						<tr>
							<th>类别</th>
							<th>IP地址范围</th>
							<th>最大主机数(根据主机号)</th>
							<th>私有IP地址范围</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>A类(0)</td>
							<td>0.0.0.0 ~ 127.255.255.255</td>
							<td>2^24 - 2 = 16777214</td>
							<td>10.0.0.0 ~ 10.255.255.255</td>
						</tr>
						<tr>
							<td>B类(10)</td>
							<td>128.0.0.0 ~ 191.255.255.255</td>
							<td>2^16 - 2 = 65534</td>
							<td>172.16.0.0 ~ 172.31.255.255</td>
						</tr>
						<tr>
							<td>C类(110)</td>
							<td>192.0.0.0 ~ 223.255.255.255</td>
							<td>2^8 - 2 = 254</td>
							<td>192.168.0.0 ~ 192.168.255.255</td>
						</tr>
					</tbody>
				</table>
				<p>主机号全为1指定某个网络下的所有主机，用于广播；主机号全为0指定某个网络</p>
				<p>私有地址，一般是我们办公室、家里、学校用的IP地址，内部IT人员管理。</p>
				<p>公有地址，由ICANN组织管理。</p>
				<ul>
					<li><strong>D、E分类</strong>：D类与E类没有主机号，所有不可用于主机IP，D类常被用于多播，E类预留的分类暂时未使用</li>
				</ul>
				<table>
					<thead>
						<tr>
							<th>类别</th>
							<th>IP地址范围</th>
							<th>用途</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>D类(1110)</td>
							<td>224.0.0.0 ~ 239.255.255.255</td>
							<td>IP多播</td>
						</tr>
						<tr>
							<td>E类(1111)</td>
							<td>240.0.0.0 ~ 255.255.255.255</td>
							<td>预留使用</td>
						</tr>
					</tbody>
				</table>
				<h4 id="-">广播与多播</h4>
				<p><strong>广播</strong>，用于在用一个链路中相互连接的主机之间发送数据包。(将主机号部分全部置为1)</p>
				<ul>
					<li>本地广播，在本网络内广播。</li>
					<li>直接广播，在不同网络之间的广播。</li>
				</ul>
				<p><strong>多播</strong>，将包发送给特定组内的所有主机。</p>
				<p><img src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/IP/13.jpg"
						style="zoom:50%;" /></p>
				<h4 id="ipv4-">IPv4地址分类优缺点</h4>
				<ul>
					<li>
						<p>优点</p>
						<ul>
							<li>简单明了、选路(基于网络地址)简单</li>
						</ul>
						<p>通过判断一二三四位是0还是1判断地址类型</p>
					</li>
					<li>
						<p>缺点</p>
						<ul>
							<li>同一网络下没有地址层次，不能根据生产环境、测试环境、开发环境划分地址层次。</li>
							<li>A、B、C类不能很好与现实网络匹配，像C类地址包含主机数太少，B类又太多。</li>
						</ul>
					</li>
				</ul>
				<h4 id="-cidr">无分类地址CIDR</h4>
				<ul>
					<li>表示形式 a.b.c.d/x，其中 /x 表示<strong>前 x 位属于网络号</strong>，x的范围是 0 ~ 32，使得IP地址更加灵活。</li>
					<li>另一种形式，通过<strong>子网掩码</strong>，掩码的意思掩盖掉主机号，剩余的就是网络号。<strong>将子网掩码和IP地址按位计算AND，就可以得到网络号</strong>
					</li>
				</ul>
				<h4 id="-">子网划分</h4>
				<p>实际上是将主机地址分为两个部分：子网网络地址和子网主机地址</p>
				<p><img src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/IP/18.jpg"
						style="zoom:67%;" /></p>
				<h4 id="ipv6">IPv6</h4>
				<p>IPv6地址128位，每16位作为一组，每组用<strong>冒号</strong>隔开。</p>
				<p>Pv4地址32位，每8位作为一组，并用点分十进制表示。</p>
				<h5 id="ipv6-"><strong>IPv6亮点</strong></h5>
				<ul>
					<li>IPv6<strong>可以自动配置</strong>，即使没有DHCP服务器也可以实现自动分配IP地址。</li>
					<li>IPv6包头包首部长度采用固定的值40字节，去掉了包头校验和，简化了首部结构，减轻了路由器符合，大大<strong>提高了传输性能。</strong></li>
					<li>IPv6有应对伪造IP地址的网络安全功能以及防止线路窃听的功能，大大<strong>提升了安全性</strong>。</li>
				</ul>
				<h5 id="ipv6-"><strong>IPv6地址主要类型</strong></h5>
				<ul>
					<li>单播地址，用于一对一的通信<ul>
							<li>在同一链路单播通信，不经过路由器，可以使用链路本地单播地址，IPv4没有此类型。</li>
							<li>在内网里单播通信，可以使用位移本地地址，相当于IPv4的私有IP</li>
							<li>在互联网通信，可以使用全局单播地址，相当于IPv4的公有IP</li>
						</ul>
					</li>
					<li>组播地址，用于一对多的通信</li>
					<li>任播地址，用于通信最近的节点，最近的节点是由路由协议决定</li>
					<li>没有广播地址</li>
				</ul>
				<h5 id="ipv4-ipv6-"><strong>IPv4首部与IPv6首部</strong></h5>
				<ul>
					<li><strong>取消了首部校验和字段。</strong>因为在数据链路层和传输层都会校验，因此IPv6直接取消了IP校验</li>
					<li><strong>取消了分片、重新组装相关字段</strong>。分片与重组是耗时的过程，Ipv6不允许在中间路由器进行分片与重组，这种操作只能在源与目标主机，这大大提高路由器转发速度。
					</li>
					<li><strong>取消选项字段。</strong>选项字段不再是标准IP首部的一部分了，但它并没有消失，而是可能出现在IPv6首部中的下一个首部指出的位置上。删除该选项字段使得IPv6的首部成为固定长度的40字节。
					</li>
				</ul>
				<h4 id="-">其他问题</h4>
				<ul>
					<li>IP地址与路由控制</li>
				</ul>
				<p>1.IP地址的网络地址这一部分是用于进行路由控制。路由控制表中记录着网络地址与下一步应该发送至路由器的地址。在主机和路由器上都会各自的路由器控制表。</p>
				<p>2.在发送IP包时，首先要确定IP包首部中的目标地址，再从路由控制表中找到与该地址具有相同网络地址的记录，根据该记录将IP包转发给相应的下一个路由器。如果路由控制表中存在多条相同网络地址的记录，就选择相同位数最多的网络地址，也就是最长匹配。
				</p>
				<ul>
					<li>IP分片与重组</li>
				</ul>
				<p>最常见数据链路层是以太网，它的MTU是<code>1500</code>字节。</p>
				<p>经过分片之后的IP数据报只会在目标主机上重组，不会在路由器上进行重组。</p>
				<p>在分片传输中，一旦某个分片丢失，则会造成整个IP数据报作废，所以TCP引入MSS也就是在TCP层进行分片不由IP层分片，那么对于UDP经历不要发送一个大于MTU的数据报文。</p>
				<ul>
					<li>环回地址</li>
				</ul>
				<p>环回地址是在同一台计算机的程序之间进行网络通信时索使用的一个默认地址。计算机使用一个特殊的IP地址<strong>127.0.0.1</strong>作为环回地址。该地址具有相同意义的是一个叫做<code>localhost</code>的主机名。使用这个IP或主机名时，数据包不会流向网络。
				</p>
				<h3 id="dns">DNS</h3>
				<h4 id="-">域名解析流程</h4>
				<p>1.浏览器缓存 2.操作系统缓存 3.检查本机域名解析文件hosts 4.查询DNS服务器 </p>
				<p>5.本地DNS服务器 6.根域名服务器 7.顶级域名服务器 8.权威域名服务器，权威域名服务器将查询的IP地址告诉本地DNS</p>
				<p>最后本地DNS再将IP地址返回客户端，客户端和目标建立连接，同时用户将TTL值缓存在本地系统缓存中。</p>
				<h4 id="-">相关问题</h4>
				<ul>
					<li>为什么域名解析用UDP</li>
				</ul>
				<p>因为UDP速度快，基于UDP的DNS协议只要一个请求，一个应答</p>
				<ul>
					<li>为什么区域传送用TCP</li>
				</ul>
				<p>区域传送，在域名系统(DNS)中，<strong>用于将一个域名完整资源记录从一个DNS服务器传输到另一个DNS服务器的过程</strong>。它允许辅助DNS服务器从注DNS服务器获取更新的DNS数据，以确保数据的一致性和可靠性。
				</p>
				<p>因为TCP可靠性好，同时TCP协议传输内容大。</p>
				<ul>
					<li>DNS负载均衡</li>
				</ul>
				<p>当一个网站有足够多的用户的时候，假如每次请求的资源都位于同一台及其上面，那么这台及其虽是可能会崩掉。</p>
				<p>处理办法就是DNS负载均衡技术，它的原理是，<strong>在DNS服务器中为同一个主机名配置多个IP地址，在应答DNS查询时，DNS服务器对每个查询将以DNS文件中主机记录的IP地址按顺序返回不同的解析结果，将客户端的访问引导到不同的机器上去，使得不同的客户端访问不同的服务器</strong>，从而达到负载均衡的目的。
				</p>
				<h3 id="arp-rarp-">ARP与RARP协议</h3>
				<ul>
					<li>ARP，查询MAC地址的主机IP地址。主机通过广播发送ARP请求，当同个链路中的所有设备收到ARP请求时，会对比请求包中的目标IP地址与自己IP地址是否一样，如果一致，那么这个设备就将自己的MAC地址塞入ARP响应包返回给主机。
					</li>
					<li>RARP，查询IP地址的MAC地址。需要架设一台RARP服务器，在这个服务器上注册设备的MAC地址及其IP地址。</li>
				</ul>
				<h3 id="dhcp">DHCP</h3>
				<p>电脑通常通过DHCP动态获取IP地址，DHCP客户端进程监听的是68端口号，DHCP服务端进程监听的是67端口号。</p>
				<p>步骤：</p>
				<ul>
					<li>客户端先发起DHCP发现报文的IP数据报(<strong>UDP广播通信</strong>，广播地址255.255.255.255，端口67，并使用0.0.0.0，端口68作为源IP地址)。DHCP客户端将IP数据报传递给链路层，链路层将帧广播到所有的网络中设备。
					</li>
					<li>DHCP服务器收到DHCP发现报文，用DHCP提供报文向客户端做出响应。广播地址仍然是255.255.255.255。该报文信息携带服务器提供可租约的IP地址、子网掩码、默认网关、DNS服务器以及IP地址租用期。
					</li>
					<li>客户端收到一个或多个服务器的DHCP提供报文后，从中选择一个服务器，并向选中的服务器发送DHCP请求报文进行响应，回显配置的参数。</li>
					<li>最后，服务端用DHCP ACK报文对DHCP请求报文进行响应，应答所要求的参数。</li>
				</ul>
				<p>一旦客户端收到DHCP ACK后，客户端能够在租用期内使用DHCP服务器分配的IP地址。</p>
				<p>由于在DHCP交互中，全程都是使用<strong>UDP广播通信</strong>。但是当DHCP服务器和客户端不再同一个局域网内，路由器又不会转发广播包。为了解决这个问题，出现了<strong>DHCP中继代理，对不同网段的IP地址分配也可以由一个DHCP服务器统一进行管理</strong>。
				</p>
				<ul>
					<li>DHCP客户端会想DHCP中继代理发送DHCP请求包，而DHCP中继代理在收到这个广播包以后，再以<strong>单播</strong>的形式发给DHCP服务器。</li>
					<li>服务器收到该包以后再向DHCP中继代理返回应答，并由DHCP中继代理将此包广播给DHCP客户端。</li>
				</ul>
				<h3 id="nat">NAT</h3>
				<p>网络地址转换NAT方法，缓解IPv4地址耗尽问题。</p>
				<p>简单来说，NAT就是同个公司、家庭、教室内的主机对外通信时，把私有IP地址转换成公有IP地址。</p>
				<p><strong>私有IP地址都转换IP地址为公有地址，但是以不同的端口号作为区别</strong>。</p>
				<p>生成一个NAPT路由器的转换表，就可以正确地转换地址跟端口组合，令客户端A、B能同时与服务器之间进行通信。这种转换表在NAT路由器上自动生成。</p>
				<p>在TCP情况下，建立TCP连接首次握手时的SYN包一经发出，就会生成这个表。而后又随着收到关闭连接时发出FIN包的确认应答从表中被删除。</p>
				<p><strong>问题</strong>：</p>
				<ul>
					<li>外部无法主动与NAT内部服务器建立连接，因为NAPT转换表没有转换记录</li>
					<li>转换表的生成与转换操作都会产生性能开销</li>
					<li>通过过程中，如果NAT路由器重启了，所有的TCP连接都将被重置。</li>
				</ul>
				<p><strong>解决方法</strong>：</p>
				<ul>
					<li>改用IPv6，由于IPv6可用范围非常大，以至于每台设备都可以配置一个公有IP地址，就不搞那么多花里胡哨的地址转换，但是IPv6普及速度还需要一些时间。</li>
					<li>NAT穿透技术，NAT穿越技术拥有这样的功能，它能够让网络应用程序主动发现自己位于NAT设备之后，并且会主动获得NAT设备的公有IP，并为自己建立端口映射条目，注意这些都是NAT设备后的应用程序自动完成的。客户端主动从NAT设备获取公有IP地址，然后自己建立端口映射条目，然后用这个条目对外通信，就不需要NAT设备来进行转换。
					</li>
				</ul>
				<h3 id="igmp">IGMP</h3>
				<p>IGMP，因特网管理协议，工作在主机(组播(多播)成员)，作用管理组播成员。</p>
				<p><strong>常规查询与响应和离开组播组</strong></p>
				<h3 id="icmp">ICMP</h3>
				<p>ICMP，<strong>互联网控制报文协议</strong></p>
				<p>ICMP报文是封装在IP包里面，工作在<strong>网络层</strong>，是IP协议的助手</p>
				<p>主要功能，<strong>确认IP包是否成功送达目标地址、报告发送过程中IP包被丢弃的原因和改善网络设置</strong>等</p>
				<p>ICMP大致分为两大类：</p>
				<ul>
					<li>一类是用于诊断的查询消息，也就是<strong>查询报文</strong>类型</li>
					<li>另一类是通知出错原因的错误消息，也就是<strong>差错报文</strong>类型</li>
				</ul>
				<table>
					<thead>
						<tr>
							<th>数字</th>
							<th>内容</th>
							<th>作用</th>
							<th>种类</th>
						</tr>
					</thead>
					<tbody>
						<tr>
							<td>0</td>
							<td>回送应答(Echo Reply)</td>
							<td>可以接收对端主机发回来的<strong>回送应答</strong>消息</td>
							<td><strong>查询报文类型</strong></td>
						</tr>
						<tr>
							<td>3</td>
							<td>目标不可达(Destination Unreachable)</td>
							<td>IP路由器无法将IP数据包发送给目标地址，消息中显示不可达的具体原因记录在ICMP的包头的代码字段中</td>
							<td>差错报文类</td>
						</tr>
						<tr>
							<td>4</td>
							<td>原点抑制(Source Quench)</td>
							<td>路由器向低速线路发送数据时，其发送队列的缓存变为零而无法发送出去时，可以向IP包的源地址发送一个ICMP<strong>原点抑制</strong>消息</td>
							<td>差错报文类型</td>
						</tr>
						<tr>
							<td>5</td>
							<td>重定向或改变路由(Redirect)</td>
							<td>路由器发现段主机使用了不是最优的路径发送数据，会返回一个ICMP重定向消息给这个主机，在这个消息中包含<strong>最合适的路由信息和源数据</strong>。</td>
							<td>差错报文类型</td>
						</tr>
						<tr>
							<td>8</td>
							<td>回送请求(Echo Request)</td>
							<td>可以向对端主机<strong>回送请求</strong>的消息</td>
							<td><strong>查询报文类型</strong></td>
						</tr>
						<tr>
							<td>11</td>
							<td>超时(Time Exceeded)</td>
							<td>IP包中有一个字段叫做TTL(time to live, 生存周期)，它的值随着每经过一次路由器就会减1，<strong>直到减到0时该IP包会被丢弃</strong>。
							</td>
							<td>差错报文类型</td>
						</tr>
					</tbody>
				</table>
				<p>目标不可达：</p>
				<ul>
					<li>网络不可达代码为 <code>0</code></li>
					<li>主机不可达代码为 <code>1</code></li>
					<li>协议不可达代码为 <code>2</code></li>
					<li>端口不可达代码为 <code>3</code></li>
					<li>需要进行分片但设置了不分片位代码为 <code>4</code></li>
				</ul>
				<h3 id="-ping-">*ping的相关知识</h3>
				<p>ping
					是应用层命令，底层用的是网络层的<strong>ICMP协议</strong>。通过向对方发送一个<strong>ICMP回送请求报文</strong>，如果对方主机可达的话会收到该报文，并响应一个<strong>ICMP回送回答报文</strong>。
				</p>
				<h4 id="-">查询报文使用</h4>
				<p><strong>第一步：</strong></p>
				<p>ping 命令执行的时候，源主机首先会构建一个 <strong>ICMP 回送请求消息</strong>数据包。</p>
				<p>ICMP 数据包内包含多个字段，最重要的是两个：</p>
				<ul>
					<li>第一个是<strong>类型</strong>，对于回送请求消息而言该字段为 <code>8</code>；</li>
					<li>另外一个是<strong>序号</strong>，主要用于区分连续 ping 的时候发出的多个数据包。</li>
				</ul>
				<p>每发出一个请求数据包，序号会自动加 <code>1</code>。为了能够计算往返时间 <code>RTT</code>，它会在报文的数据部分插入发送时间。</p>
				<p><strong>第二步：</strong></p>
				<p>然后，由 ICMP 协议将这个数据包连同地址 192.168.1.2 一起交给 IP 层。IP 层将以 192.168.1.2 作为<strong>目的地址</strong>，本机 IP
					地址作为<strong>源地址</strong>，<strong>协议</strong>字段设置为 <code>1</code> 表示是 <code>ICMP</code>
					协议，再加上一些其他控制信息，构建一个 <code>IP</code> 数据包。</p>
				<p><strong>第三步：</strong></p>
				<p>接下来，需要加入 <code>MAC</code> 头。如果在本地 ARP 映射表中查找出 IP 地址 192.168.1.2 所对应的 MAC 地址，则可以直接使用；如果没有，则需要发送
					<code>ARP</code> 协议查询 MAC 地址，获得 MAC 地址后，由数据链路层构建一个数据帧，目的地址是 IP 层传过来的 MAC 地址，源地址则是本机的 MAC
					地址；还要附加上一些控制信息，依据以太网的介质访问规则，将它们传送出去。</p>
				<p><strong>第四步：</strong></p>
				<p>主机 <code>B</code> 收到这个数据帧后，先检查它的目的 MAC 地址，并和本机的 MAC 地址对比，如符合，则接收，否则就丢弃。</p>
				<p>接收后检查该数据帧，将 IP 数据包从帧中提取出来，交给本机的 IP 层。同样，IP 层检查后，将有用的信息提取后交给 ICMP 协议。</p>
				<p>主机 <code>B</code> 会构建一个 <strong>ICMP 回送响应消息</strong>数据包，回送响应数据包的<strong>类型</strong>字段为
					<code>0</code>，<strong>序号</strong>为接收到的请求数据包中的序号，然后再发送出去给主机 A。</p>
				<p><strong>最后判断：</strong></p>
				<p>在规定的时候间内，源主机如果没有接到 ICMP 的应答包，则说明目标主机不可达；如果接收到了 ICMP 回送响应消息，则说明目标主机可达。</p>
				<p>此时，源主机会检查，用当前时刻减去该数据包最初从源主机上发出的时刻，就是 ICMP 数据包的时间延迟。</p>
				<p><img src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/ping/17.png"
						style="zoom: 50%;" /></p>
				<h4 id="-">差错报文使用</h4>
				<p><code>traceroute</code>充分利用ICMP差错报文类型的应用。</p>
				<p>作用：</p>
				<ul>
					<li>故意设置特殊的TTL，来追踪去往目的地沿途经过的路由器。</li>
				</ul>
				<p>发送方在发送UDP包时，会填入一个不可能的端口号，当目的主机收到UDP包后会返回ICMP差错报文消息（端口不可达）说明UDP包到达目的主机。</p>
				<ul>
					<li>故意设备不分片，从而确定路径的MTU</li>
				</ul>
				<p>首先在发送端主机发送 <code>IP</code> 数据报时，将 <code>IP</code> 包首部的<strong>分片禁止标志位设置为
						1</strong>。根据这个标志位，途中的路由器不会对大数据包进行分片，而是将包丢弃。</p>
				<p>随后，通过一个 ICMP 的不可达消息将<strong>数据链路上 MTU 的值</strong>一起给发送主机，不可达消息的类型为「<strong>需要进行分片但设置了不分片位</strong>」。
				</p>
				<p>发送主机端每次收到 ICMP 差错报文时就<strong>减少</strong>包的大小，以此来定位一个合适的 <code>MTU</code> 值，以便能到达目标主机。</p>
				<h4 id="-">相关问题</h4>
				<ul>
					<li>断网了，还能ping同127.0.0.1吗</li>
				</ul>
				<p>127开头都属于回环地址(计算机以回环地址发送的消息，并不会由链路层送走，而是被本机网络层捕获)。</p>
				<p>有网的时候，ping最后是通过网卡发送的。</p>
				<p>没有网的时候，会选择<strong>本地网卡</strong>发送，本地网卡会将数据推到一个叫<strong>input_pkt_queue的链表</strong>中，这个链表是所有网卡共享的，上面挂着发给本机的各种消息，消息被发送到这个链表后，会触发一个软中断，内核收到软中断就会将消息一层层传给应用程序。
				</p>
				<ul>
					<li>ping回环地址和ping本机地址有什么区别</li>
				</ul>
				<p>lo0，回环地址；eth0，本机IP。</p>
				<p><strong>ping这两个地址，最后走的都是lo0本地回环接口。</strong></p>
				<ul>
					<li>127.0.0.1 和 localhost 以及 0.0.0.0 有区别吗</li>
				</ul>
				<p>localhost，默认下是127.0.0.1，但是可以在/etc/hosts文件下修改。</p>
				<p>0.0.0.0，在IPv4中表示为无效目标地址。但是如果此时listen的是本机的 0.0.0.0 ,
					那么它表示本机上的<strong>所有IPV4地址</strong>。那么此时用127.0.0.1和本机地址<strong>都可以</strong>访问到服务。</p>
				<ul>
					<li>TCP发数据和ping的区别</li>
				</ul>
				<p><img src="https://cdn.xiaolincoding.com//mysql/other/eb0963a11439dff361dbe0e7a8876abd.png"
						style="zoom: 67%;" /></p>
				<p>整个过程也基本跟 <code>TCP</code> 发数据类似，差异的地方主要在于，创建 <code>socket</code> 的时候用的是
					<code>socket(AF_INET,SOCK_RAW,IPPROTO_ICMP)</code>，<code>SOCK_RAW</code> 是原始套接字
					，<strong>工作在网络层</strong>， 所以构建<code>ICMP</code>（网络层协议）的数据，是再合适不过了。</p>

			</article>
		</div>
</body>
</html>